added clock to patch, some packaging cleanup
This commit is contained in:
parent
f9142d6a1a
commit
95cb760f79
10 changed files with 34 additions and 2067 deletions
automerge-backend-wasm
automerge-backend/src
|
@ -3,7 +3,7 @@
|
|||
name = "automerge-backend-wasm"
|
||||
description = ""
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Good <alex@memoryandthought.me>","Orion Henry <orion@inkandswitch.com>"]
|
||||
authors = ["Alex Good <alex@memoryandthought.me>","Orion Henry <orion@inkandswitch.com>", "Martin Kleppmann"]
|
||||
categories = ["wasm"]
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
@ -11,24 +11,17 @@ edition = "2018"
|
|||
[lib]
|
||||
crate-type = ["cdylib","rlib"]
|
||||
|
||||
|
||||
[features]
|
||||
# If you uncomment this line, it will enable `wee_alloc`:
|
||||
#default = ["wee_alloc"]
|
||||
default = ["console_error_panic_hook", "wee_alloc"]
|
||||
|
||||
[dependencies]
|
||||
# The `wasm-bindgen` crate provides the bare minimum functionality needed
|
||||
# to interact with JavaScript.
|
||||
console_error_panic_hook = { version = "^0.1", optional = true }
|
||||
wee_alloc = { version = "^0.4", optional = true }
|
||||
automerge-backend = { path = "../automerge-backend" }
|
||||
js-sys = "^0.3"
|
||||
serde = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. However, it is slower than the default
|
||||
# allocator, so it's not enabled by default.
|
||||
wee_alloc = { version = "0.4.2", optional = true }
|
||||
|
||||
[dependencies.wasm-bindgen]
|
||||
version = "^0.2"
|
||||
features = ["serde-serialize"]
|
||||
|
@ -39,14 +32,6 @@ features = ["serde-serialize"]
|
|||
version = "0.3.22"
|
||||
features = ["console"]
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled
|
||||
# in debug mode.
|
||||
[target."cfg(debug_assertions)".dependencies]
|
||||
console_error_panic_hook = "0.1.5"
|
||||
|
||||
# These crates are used for running unit tests.
|
||||
[dev-dependencies]
|
||||
futures = "^0.1"
|
||||
wasm-bindgen-futures = "^0.3"
|
||||
|
|
|
@ -1,48 +1,25 @@
|
|||
## How to install
|
||||
## automerge-backend-wasm
|
||||
|
||||
This is a npm wrapper for the rust implementation of [automerge-backend](https://github.com/automerge/automerge-rs/tree/master/automerge-backend).
|
||||
It is currently experimental and in development against the Automerge performance branch.
|
||||
|
||||
### building
|
||||
|
||||
Make sure you have the latest rust compiler installed (1.42.0 or later).
|
||||
|
||||
```sh
|
||||
npm install
|
||||
cargo install wasm-pack
|
||||
yarn install
|
||||
```
|
||||
|
||||
## How to run in debug mode
|
||||
Then build the debug version with
|
||||
|
||||
```sh
|
||||
# Builds the project and opens it in a new browser tab. Auto-reloads when the project changes.
|
||||
npm start
|
||||
yarn build
|
||||
```
|
||||
|
||||
## How to build in release mode
|
||||
or the release build with
|
||||
|
||||
```sh
|
||||
# Builds the project and places it into the `dist` folder.
|
||||
npm run build
|
||||
yarn release
|
||||
```
|
||||
|
||||
## How to run unit tests
|
||||
|
||||
```sh
|
||||
# Runs tests in Firefox
|
||||
npm test -- --firefox
|
||||
|
||||
# Runs tests in Chrome
|
||||
npm test -- --chrome
|
||||
|
||||
# Runs tests in Safari
|
||||
npm test -- --safari
|
||||
```
|
||||
|
||||
## What does each file do?
|
||||
|
||||
* `Cargo.toml` contains the standard Rust metadata. You put your Rust dependencies in here. You must change this file with your details (name, description, version, authors, categories)
|
||||
|
||||
* `package.json` contains the standard npm metadata. You put your JavaScript dependencies in here. You must change this file with your details (author, name, version)
|
||||
|
||||
* `webpack.config.js` contains the Webpack configuration. You shouldn't need to change this, unless you have very special needs.
|
||||
|
||||
* The `js` folder contains your JavaScript code (`index.js` is used to hook everything into Webpack, you don't need to change it).
|
||||
|
||||
* The `src` folder contains your Rust code.
|
||||
|
||||
* The `static` folder contains any files that you want copied as-is into the final build. It contains an `index.html` file which loads the `index.js` file.
|
||||
|
||||
* The `tests` folder contains your Rust unit tests.
|
||||
|
|
|
@ -1,565 +0,0 @@
|
|||
const { ROOT_ID, copyObject, parseOpId } = require('./common')
|
||||
const { Encoder, Decoder, RLEEncoder, RLEDecoder, DeltaEncoder, DeltaDecoder, BooleanEncoder, BooleanDecoder } = require('./encoding')
|
||||
|
||||
// Maybe we should be using the platform's built-in hash implementation?
|
||||
// Node has the crypto module: https://nodejs.org/api/crypto.html and browsers have
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
|
||||
// However, the WebCrypto API is asynchronous (returns promises), which would
|
||||
// force all our APIs to become asynchronous as well, which would be annoying.
|
||||
//
|
||||
// I think on balance, it's safe enough to use a random library off npm:
|
||||
// - We only need one hash function (not a full suite of crypto algorithms);
|
||||
// - SHA256 is quite simple and has fairly few opportunities for subtle bugs
|
||||
// (compared to asymmetric cryptography anyway);
|
||||
// - It does not need a secure source of random bits and does not need to be
|
||||
// constant-time;
|
||||
// - I have reviewed the source code and it seems pretty reasonable.
|
||||
const { Hash } = require('fast-sha256')
|
||||
|
||||
// These bytes don't mean anything, they were generated randomly
|
||||
const MAGIC_BYTES = Uint8Array.of(0x85, 0x6f, 0x4a, 0x83)
|
||||
|
||||
const COLUMN_TYPE = {
|
||||
GROUP_CARD: 0, ACTOR_ID: 1, INT_RLE: 2, INT_DELTA: 3, BOOLEAN: 4,
|
||||
STRING_RLE: 5, VALUE_LEN: 6, VALUE_RAW: 7
|
||||
}
|
||||
|
||||
const VALUE_TYPE = {
|
||||
NULL: 0, FALSE: 1, TRUE: 2, LEB128_UINT: 3, LEB128_INT: 4, IEEE754: 5,
|
||||
UTF8: 6, BYTES: 7, COUNTER: 8, TIMESTAMP: 9, MIN_UNKNOWN: 10, MAX_UNKNOWN: 15
|
||||
}
|
||||
|
||||
const ACTIONS = ['set', 'del', 'inc', 'link', 'makeMap', 'makeList', 'makeText', 'makeTable']
|
||||
|
||||
const CHANGE_COLUMNS = {
|
||||
objActor: 0 << 3 | COLUMN_TYPE.ACTOR_ID,
|
||||
objCtr: 0 << 3 | COLUMN_TYPE.INT_RLE,
|
||||
keyActor: 1 << 3 | COLUMN_TYPE.ACTOR_ID,
|
||||
keyCtr: 1 << 3 | COLUMN_TYPE.INT_DELTA,
|
||||
keyStr: 1 << 3 | COLUMN_TYPE.STRING_RLE,
|
||||
idActor: 2 << 3 | COLUMN_TYPE.ACTOR_ID,
|
||||
idCtr: 2 << 3 | COLUMN_TYPE.INT_RLE,
|
||||
insert: 3 << 3 | COLUMN_TYPE.BOOLEAN,
|
||||
action: 4 << 3 | COLUMN_TYPE.INT_RLE,
|
||||
valLen: 5 << 3 | COLUMN_TYPE.VALUE_LEN,
|
||||
valRaw: 5 << 3 | COLUMN_TYPE.VALUE_RAW,
|
||||
chldActor: 6 << 3 | COLUMN_TYPE.ACTOR_ID,
|
||||
chldCtr: 6 << 3 | COLUMN_TYPE.INT_RLE,
|
||||
predNum: 7 << 3 | COLUMN_TYPE.GROUP_CARD,
|
||||
predActor: 7 << 3 | COLUMN_TYPE.ACTOR_ID,
|
||||
predCtr: 7 << 3 | COLUMN_TYPE.INT_RLE,
|
||||
succNum: 8 << 3 | COLUMN_TYPE.GROUP_CARD,
|
||||
succActor: 8 << 3 | COLUMN_TYPE.ACTOR_ID,
|
||||
succCtr: 8 << 3 | COLUMN_TYPE.INT_RLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the two byte arrays contain the same data, false if not.
|
||||
*/
|
||||
function compareBytes(array1, array2) {
|
||||
if (array1.byteLength !== array2.byteLength) return false
|
||||
for (let i = 0; i < array1.byteLength; i++) {
|
||||
if (array1[i] !== array2[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string of the form '12345@someActorId' into an object of the form
|
||||
* {counter: 12345, actorId: 'someActorId'}, and any other string into an object
|
||||
* of the form {value: 'originalString'}.
|
||||
*/
|
||||
function maybeParseOpId(value) {
|
||||
if (value === undefined) return {}
|
||||
// FIXME when parsing the "key" of an operation, need to correctly handle
|
||||
// map property names that happen to contain an @ sign
|
||||
return (value.indexOf('@') >= 0) ? parseOpId(value) : {value}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an opId of the form {counter: 12345, actorId: 'someActorId'} to the form
|
||||
* {counter: 12345, actorNum: 123}, where the actorNum is zero for the actor
|
||||
* `ownActor`, and the (1-based) index into the `actorIds` array otherwise.
|
||||
*/
|
||||
function actorIdToActorNum(opId, ownActor, actorIds) {
|
||||
if (!opId.actorId) return opId
|
||||
const counter = opId.counter
|
||||
if (opId.actorId === ownActor) return {counter, actorNum: 0}
|
||||
const actorNum = actorIds.indexOf(opId.actorId) + 1
|
||||
if (actorNum === 0) throw new RangeError('missing actorId') // should not happen
|
||||
return {counter, actorNum}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object `{change, actorIds}` where `change` is a copy of the argument
|
||||
* in which all string opIds have been replaced with `{counter, actorNum}` objects,
|
||||
* and where `actorIds` is a lexicographically sorted array of actor IDs occurring
|
||||
* in any of the operations, excluding the actorId of the change itself. An
|
||||
* `actorNum` value of zero indicates the actorId is the author of the change
|
||||
* itself, and an `actorNum` greater than zero is an index into the array of
|
||||
* actorIds (indexed starting from 1).
|
||||
*/
|
||||
function parseAllOpIds(change) {
|
||||
const actors = {}
|
||||
change = copyObject(change)
|
||||
for (let actor of Object.keys(change.deps)) actors[actor] = true
|
||||
change.ops = change.ops.map(op => {
|
||||
op = copyObject(op)
|
||||
op.obj = maybeParseOpId(op.obj)
|
||||
op.key = maybeParseOpId(op.key)
|
||||
op.child = maybeParseOpId(op.child)
|
||||
if (op.pred) op.pred = op.pred.map(parseOpId)
|
||||
if (op.obj.actorId) actors[op.obj.actorId] = true
|
||||
if (op.key.actorId) actors[op.key.actorId] = true
|
||||
if (op.child.actorId) actors[op.child.actorId] = true
|
||||
for (let pred of op.pred) actors[pred.actorId] = true
|
||||
return op
|
||||
})
|
||||
const actorIds = Object.keys(actors).filter(actor => actor !== change.actor).sort()
|
||||
for (let op of change.ops) {
|
||||
op.obj = actorIdToActorNum(op.obj, change.actor, actorIds)
|
||||
op.key = actorIdToActorNum(op.key, change.actor, actorIds)
|
||||
op.pred = op.pred.map(pred => actorIdToActorNum(pred, change.actor, actorIds))
|
||||
}
|
||||
return {change, actorIds}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the `obj` property of operation `op` into the two columns
|
||||
* `objActor` and `objCtr`.
|
||||
*/
|
||||
function encodeObjectId(op, columns) {
|
||||
if (op.obj.value === ROOT_ID) {
|
||||
columns.objActor.appendValue(null)
|
||||
columns.objCtr.appendValue(null)
|
||||
} else if (op.obj.actorNum >= 0 && op.obj.counter > 0) {
|
||||
columns.objActor.appendValue(op.obj.actorNum)
|
||||
columns.objCtr.appendValue(op.obj.counter)
|
||||
} else {
|
||||
throw new RangeError(`Unexpected objectId reference: ${JSON.stringify(op.obj)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the `key` property of operation `op` into the three columns
|
||||
* `keyActor`, `keyCtr`, and `keyStr`.
|
||||
*/
|
||||
function encodeOperationKey(op, columns) {
|
||||
if (op.key.value === '_head' && op.insert) {
|
||||
columns.keyActor.appendValue(0)
|
||||
columns.keyCtr.appendValue(0)
|
||||
columns.keyStr.appendValue(null)
|
||||
} else if (op.key.value) {
|
||||
columns.keyActor.appendValue(null)
|
||||
columns.keyCtr.appendValue(null)
|
||||
columns.keyStr.appendValue(op.key.value)
|
||||
} else if (op.key.actorNum >= 0 && op.key.counter > 0) {
|
||||
columns.keyActor.appendValue(op.key.actorNum)
|
||||
columns.keyCtr.appendValue(op.key.counter)
|
||||
columns.keyStr.appendValue(null)
|
||||
} else {
|
||||
throw new RangeError(`Unexpected operation key: ${JSON.stringify(op.key)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the `action` property of operation `op` into the `action` column.
|
||||
*/
|
||||
function encodeOperationAction(op, columns) {
|
||||
const actionCode = ACTIONS.indexOf(op.action)
|
||||
if (actionCode >= 0) {
|
||||
columns.action.appendValue(actionCode)
|
||||
} else if (typeof op.action === 'number') {
|
||||
columns.action.appendValue(op.action)
|
||||
} else {
|
||||
throw new RangeError(`Unexpected operation action: ${op.action}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the integer `value` into the two columns `valLen` and `valRaw`,
|
||||
* with the datatype tag set to `typeTag`. If `typeTag` is zero, it is set
|
||||
* automatically to signed or unsigned depending on the sign of the value.
|
||||
* Values with non-zero type tags are always encoded as signed integers.
|
||||
*/
|
||||
function encodeInteger(value, typeTag, columns) {
|
||||
let numBytes
|
||||
if (value < 0 || typeTag > 0) {
|
||||
numBytes = columns.valRaw.appendInt53(value)
|
||||
if (!typeTag) typeTag = VALUE_TYPE.LEB128_INT
|
||||
} else {
|
||||
numBytes = columns.valRaw.appendUint53(value)
|
||||
typeTag = VALUE_TYPE.LEB128_UINT
|
||||
}
|
||||
columns.valLen.appendValue(numBytes << 4 | typeTag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the `value` property of operation `op` into the two columns
|
||||
* `valLen` and `valRaw`.
|
||||
*/
|
||||
function encodeValue(op, columns) {
|
||||
if ((op.action !== 'set' && op.action !== 'inc') || op.value === null) {
|
||||
columns.valLen.appendValue(VALUE_TYPE.NULL)
|
||||
} else if (op.value === false) {
|
||||
columns.valLen.appendValue(VALUE_TYPE.FALSE)
|
||||
} else if (op.value === true) {
|
||||
columns.valLen.appendValue(VALUE_TYPE.TRUE)
|
||||
} else if (typeof op.value === 'string') {
|
||||
const numBytes = columns.valRaw.appendRawString(op.value)
|
||||
columns.valLen.appendValue(numBytes << 4 | VALUE_TYPE.UTF8)
|
||||
} else if (ArrayBuffer.isView(op.value)) {
|
||||
const numBytes = columns.valRaw.appendRawBytes(new Uint8Array(op.value.buffer))
|
||||
columns.valLen.appendValue(numBytes << 4 | VALUE_TYPE.BYTES)
|
||||
} else if (op.datatype === 'counter' && typeof op.value === 'number') {
|
||||
encodeInteger(op.value, VALUE_TYPE.COUNTER, columns)
|
||||
} else if (op.datatype === 'timestamp' && typeof op.value === 'number') {
|
||||
encodeInteger(op.value, VALUE_TYPE.TIMESTAMP, columns)
|
||||
} else if (typeof op.datatype === 'number' && op.datatype >= VALUE_TYPE.MIN_UNKNOWN &&
|
||||
op.datatype <= VALUE_TYPE.MAX_UNKNOWN && op.value instanceof Uint8Array) {
|
||||
const numBytes = columns.valRaw.appendRawBytes(op.value)
|
||||
columns.valLen.appendValue(numBytes << 4 | op.datatype)
|
||||
} else if (op.datatype) {
|
||||
throw new RangeError(`Unknown datatype ${op.datatype} for value ${op.value}`)
|
||||
} else if (typeof op.value === 'number') {
|
||||
if (Number.isInteger(op.value) && op.value <= Number.MAX_SAFE_INTEGER && op.value >= Number.MIN_SAFE_INTEGER) {
|
||||
encodeInteger(op.value, 0, columns)
|
||||
} else {
|
||||
// Encode number in 32-bit float if this can be done without loss of precision
|
||||
const buf32 = new ArrayBuffer(4), view32 = new DataView(buf32)
|
||||
view32.setFloat32(0, op.value, true) // true means little-endian
|
||||
if (view32.getFloat32(0, true) === op.value) {
|
||||
columns.valRaw.appendRawBytes(new Uint8Array(buf32))
|
||||
columns.valLen.appendValue(4 << 4 | VALUE_TYPE.IEEE754)
|
||||
} else {
|
||||
const buf64 = new ArrayBuffer(8), view64 = new DataView(buf64)
|
||||
view64.setFloat64(0, op.value, true) // true means little-endian
|
||||
columns.valRaw.appendRawBytes(new Uint8Array(buf64))
|
||||
columns.valLen.appendValue(8 << 4 | VALUE_TYPE.IEEE754)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RangeError(`Unsupported value in operation: ${op.value}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one value from the column `columns[colIndex]` and interprets it based
|
||||
* on the column type. `actorIds` is a list of actors that appear in the change;
|
||||
* `actorIds[0]` is the actorId of the change's author. Mutates the `value`
|
||||
* object with the value, and returns the number of columns processed (this is 2
|
||||
* in the case of a pair of VALUE_LEN and VALUE_RAW columns, which are processed
|
||||
* in one go).
|
||||
*/
|
||||
function decodeValue(columns, colIndex, actorIds, value) {
|
||||
const { columnId, columnName, decoder } = columns[colIndex]
|
||||
if (columnId % 8 === COLUMN_TYPE.VALUE_LEN && colIndex + 1 < columns.length &&
|
||||
columns[colIndex + 1].columnId === columnId + 1) {
|
||||
const sizeTag = decoder.readValue(), rawDecoder = columns[colIndex + 1].decoder
|
||||
if (sizeTag === VALUE_TYPE.NULL) {
|
||||
value[columnName] = null
|
||||
} else if (sizeTag === VALUE_TYPE.FALSE) {
|
||||
value[columnName] = false
|
||||
} else if (sizeTag === VALUE_TYPE.TRUE) {
|
||||
value[columnName] = true
|
||||
} else if (sizeTag % 16 === VALUE_TYPE.UTF8) {
|
||||
value[columnName] = rawDecoder.readRawString(sizeTag >> 4)
|
||||
} else {
|
||||
const bytes = rawDecoder.readRawBytes(sizeTag >> 4), valDecoder = new Decoder(bytes)
|
||||
if (sizeTag % 16 === VALUE_TYPE.LEB128_UINT) {
|
||||
value[columnName] = valDecoder.readUint53()
|
||||
} else if (sizeTag % 16 === VALUE_TYPE.LEB128_INT) {
|
||||
value[columnName] = valDecoder.readInt53()
|
||||
} else if (sizeTag % 16 === VALUE_TYPE.IEEE754) {
|
||||
const view = new DataView(bytes.buffer)
|
||||
if (bytes.byteLength === 4) {
|
||||
value[columnName] = view.getFloat32(0, true) // true means little-endian
|
||||
} else if (bytes.byteLength === 8) {
|
||||
value[columnName] = view.getFloat64(0, true)
|
||||
} else {
|
||||
throw new RangeError(`Invalid length for floating point number: ${bytes.byteLength}`)
|
||||
}
|
||||
} else if (sizeTag % 16 === VALUE_TYPE.COUNTER) {
|
||||
value[columnName] = valDecoder.readInt53()
|
||||
value[columnName + '_datatype'] = 'counter'
|
||||
} else if (sizeTag % 16 === VALUE_TYPE.TIMESTAMP) {
|
||||
value[columnName] = valDecoder.readInt53()
|
||||
value[columnName + '_datatype'] = 'timestamp'
|
||||
} else {
|
||||
value[columnName] = bytes
|
||||
value[columnName + '_datatype'] = sizeTag % 16
|
||||
}
|
||||
}
|
||||
return 2
|
||||
} else if (columnId % 8 === COLUMN_TYPE.ACTOR_ID) {
|
||||
value[columnName] = actorIds[decoder.readValue()]
|
||||
} else {
|
||||
value[columnName] = decoder.readValue()
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an array of operations in a set of columns. The operations need to
|
||||
* be parsed with `parseAllOpIds()` beforehand. Returns a map from column name
|
||||
* to Encoder object.
|
||||
*/
|
||||
function encodeOps(ops) {
|
||||
const columns = {
|
||||
objActor : new RLEEncoder('uint'),
|
||||
objCtr : new RLEEncoder('uint'),
|
||||
keyActor : new RLEEncoder('uint'),
|
||||
keyCtr : new DeltaEncoder(),
|
||||
keyStr : new RLEEncoder('utf8'),
|
||||
insert : new BooleanEncoder(),
|
||||
action : new RLEEncoder('uint'),
|
||||
valLen : new RLEEncoder('uint'),
|
||||
valRaw : new Encoder(),
|
||||
chldActor : new RLEEncoder('uint'),
|
||||
chldCtr : new RLEEncoder('uint'),
|
||||
predNum : new RLEEncoder('uint'),
|
||||
predCtr : new RLEEncoder('uint'),
|
||||
predActor : new RLEEncoder('uint')
|
||||
}
|
||||
|
||||
for (let op of ops) {
|
||||
encodeObjectId(op, columns)
|
||||
encodeOperationKey(op, columns)
|
||||
columns.insert.appendValue(!!op.insert)
|
||||
encodeOperationAction(op, columns)
|
||||
encodeValue(op, columns)
|
||||
|
||||
if (op.child.counter) {
|
||||
columns.chldActor.appendValue(op.child.actorNum)
|
||||
columns.chldCtr.appendValue(op.child.counter)
|
||||
} else {
|
||||
columns.chldActor.appendValue(null)
|
||||
columns.chldCtr.appendValue(null)
|
||||
}
|
||||
|
||||
columns.predNum.appendValue(op.pred.length)
|
||||
for (let i = 0; i < op.pred.length; i++) {
|
||||
columns.predActor.appendValue(op.pred[i].actorNum)
|
||||
columns.predCtr.appendValue(op.pred[i].counter)
|
||||
}
|
||||
}
|
||||
return columns
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a change as decoded by `decodeColumns`, and changes it into the form
|
||||
* expected by the rest of the backend.
|
||||
*/
|
||||
function decodeOps(ops) {
|
||||
const newOps = []
|
||||
for (let op of ops) {
|
||||
const newOp = {
|
||||
obj: op.objCtr === null ? ROOT_ID : `${op.objCtr}@${op.objActor}`,
|
||||
key: op.keyCtr === 0 ? '_head' : (op.keyStr || `${op.keyCtr}@${op.keyActor}`),
|
||||
action: ACTIONS[op.action] || op.action,
|
||||
pred: op.predNum.map(pred => `${pred.predCtr}@${pred.predActor}`)
|
||||
}
|
||||
if (op.insert) newOp.insert = true
|
||||
if (ACTIONS[op.action] === 'set' || ACTIONS[op.action] === 'inc') {
|
||||
newOp.value = op.valLen
|
||||
if (op.valLen_datatype) newOp.datatype = op.valLen_datatype
|
||||
}
|
||||
if (op.chldCtr !== null) newOp.child = `${op.chldCtr}@${op.chldActor}`
|
||||
newOps.push(newOp)
|
||||
}
|
||||
return newOps
|
||||
}
|
||||
|
||||
function decoderByColumnId(columnId, buffer) {
|
||||
if ((columnId & 7) === COLUMN_TYPE.INT_DELTA) {
|
||||
return new DeltaDecoder(buffer)
|
||||
} else if ((columnId & 7) === COLUMN_TYPE.BOOLEAN) {
|
||||
return new BooleanDecoder(buffer)
|
||||
} else if ((columnId & 7) === COLUMN_TYPE.STRING_RLE) {
|
||||
return new RLEDecoder('utf8', buffer)
|
||||
} else if ((columnId & 7) === COLUMN_TYPE.VALUE_RAW) {
|
||||
return new Decoder(buffer)
|
||||
} else {
|
||||
return new RLEDecoder('uint', buffer)
|
||||
}
|
||||
}
|
||||
|
||||
function decodeColumns(decoder, actorIds) {
|
||||
// By default, every column decodes an empty byte array
|
||||
const emptyBuf = Uint8Array.of(), decoders = {}
|
||||
for (let [columnName, columnId] of Object.entries(CHANGE_COLUMNS)) {
|
||||
decoders[columnId] = decoderByColumnId(columnId, emptyBuf)
|
||||
}
|
||||
|
||||
let lastColumnId = -1
|
||||
while (!decoder.done) {
|
||||
const columnId = decoder.readUint32()
|
||||
const columnBuf = decoder.readPrefixedBytes()
|
||||
if (columnId <= lastColumnId) throw new RangeError('Columns must be in ascending order')
|
||||
lastColumnId = columnId
|
||||
decoders[columnId] = decoderByColumnId(columnId, columnBuf)
|
||||
}
|
||||
|
||||
let columns = []
|
||||
for (let columnId of Object.keys(decoders).map(id => parseInt(id)).sort()) {
|
||||
let [columnName, _] = Object.entries(CHANGE_COLUMNS).find(([name, id]) => id === columnId)
|
||||
if (!columnName) columnName = columnId.toString()
|
||||
columns.push({columnId, columnName, decoder: decoders[columnId]})
|
||||
}
|
||||
|
||||
let parsedOps = []
|
||||
while (columns.some(col => !col.decoder.done)) {
|
||||
let op = {}, col = 0
|
||||
while (col < columns.length) {
|
||||
const columnId = columns[col].columnId
|
||||
let groupId = columnId >> 3, groupCols = 1
|
||||
while (col + groupCols < columns.length && columns[col + groupCols].columnId >> 3 === groupId) {
|
||||
groupCols++
|
||||
}
|
||||
|
||||
if (columnId % 8 === COLUMN_TYPE.GROUP_CARD) {
|
||||
const values = [], count = columns[col].decoder.readValue()
|
||||
for (let i = 0; i < count; i++) {
|
||||
let value = {}
|
||||
for (let colOffset = 1; colOffset < groupCols; colOffset++) {
|
||||
decodeValue(columns, col + colOffset, actorIds, value)
|
||||
}
|
||||
values.push(value)
|
||||
}
|
||||
op[columns[col].columnName] = values
|
||||
col += groupCols
|
||||
} else {
|
||||
col += decodeValue(columns, col, actorIds, op)
|
||||
}
|
||||
}
|
||||
parsedOps.push(op)
|
||||
}
|
||||
return parsedOps
|
||||
}
|
||||
|
||||
function encodeChangeHeader(encoder, change, actorIds) {
|
||||
encoder.appendPrefixedString(change.actor)
|
||||
encoder.appendUint53(change.seq)
|
||||
encoder.appendUint53(change.startOp)
|
||||
encoder.appendInt53(change.time)
|
||||
encoder.appendPrefixedString(change.message || '')
|
||||
encoder.appendUint53(actorIds.length)
|
||||
for (let actor of actorIds) encoder.appendPrefixedString(actor)
|
||||
const depsKeys = Object.keys(change.deps).sort()
|
||||
encoder.appendUint53(depsKeys.length)
|
||||
for (let actor of depsKeys) {
|
||||
encoder.appendUint53(actorIds.indexOf(actor) + 1)
|
||||
encoder.appendUint53(change.deps[actor])
|
||||
}
|
||||
}
|
||||
|
||||
function decodeChangeHeader(decoder) {
|
||||
let change = {
|
||||
actor: decoder.readPrefixedString(),
|
||||
seq: decoder.readUint53(),
|
||||
startOp: decoder.readUint53(),
|
||||
time: decoder.readInt53(),
|
||||
message: decoder.readPrefixedString(),
|
||||
deps: {}
|
||||
}
|
||||
const actorIds = [change.actor], numActorIds = decoder.readUint53()
|
||||
for (let i = 0; i < numActorIds; i++) actorIds.push(decoder.readPrefixedString())
|
||||
const numDeps = decoder.readUint53()
|
||||
for (let i = 0; i < numDeps; i++) {
|
||||
change.deps[actorIds[decoder.readUint53()]] = decoder.readUint53()
|
||||
}
|
||||
change.ops = decodeOps(decodeColumns(decoder, actorIds))
|
||||
return change
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the `callback` with an encoder that should be used to encode the
|
||||
* contents of the container.
|
||||
*/
|
||||
function encodeContainerHeader(chunkType, callback) {
|
||||
const HASH_SIZE = 32 // size of SHA-256 hash
|
||||
const HEADER_SPACE = MAGIC_BYTES.byteLength + HASH_SIZE + 1 + 5 // 1 byte type + 5 bytes length
|
||||
const body = new Encoder()
|
||||
// Make space for the header at the beginning of the body buffer. We will
|
||||
// copy the header in here later. This is cheaper than copying the body since
|
||||
// the body is likely to be much larger than the header.
|
||||
body.appendRawBytes(new Uint8Array(HEADER_SPACE))
|
||||
callback(body)
|
||||
const bodyBuf = body.buffer
|
||||
|
||||
const header = new Encoder()
|
||||
if (chunkType === 'document') {
|
||||
header.appendByte(0)
|
||||
} else if (chunkType === 'change') {
|
||||
header.appendByte(1)
|
||||
} else {
|
||||
throw new RangeError(`Unsupported chunk type: ${chunkType}`)
|
||||
}
|
||||
header.appendUint53(bodyBuf.byteLength - HEADER_SPACE)
|
||||
|
||||
// Compute the hash over chunkType, length, and body
|
||||
const headerBuf = header.buffer
|
||||
const hash = new Hash()
|
||||
hash.update(headerBuf)
|
||||
hash.update(bodyBuf.subarray(HEADER_SPACE))
|
||||
|
||||
// Copy header into the body buffer so that they are contiguous
|
||||
bodyBuf.set(MAGIC_BYTES, HEADER_SPACE - headerBuf.byteLength - HASH_SIZE - MAGIC_BYTES.byteLength)
|
||||
bodyBuf.set(hash.digest(), HEADER_SPACE - headerBuf.byteLength - HASH_SIZE)
|
||||
bodyBuf.set(headerBuf, HEADER_SPACE - headerBuf.byteLength)
|
||||
//console.log('hash: ', [...hash.digest()].map(x => `0x${x.toString(16)}`).join(', '))
|
||||
return bodyBuf.subarray( HEADER_SPACE - headerBuf.byteLength - HASH_SIZE - MAGIC_BYTES.byteLength)
|
||||
}
|
||||
|
||||
function decodeContainerHeader(decoder) {
|
||||
if (!compareBytes(decoder.readRawBytes(MAGIC_BYTES.byteLength), MAGIC_BYTES)) {
|
||||
throw new RangeError('Data does not begin with magic bytes 85 6f 4a 83')
|
||||
}
|
||||
const expectedHash = decoder.readRawBytes(32)
|
||||
const hashStartOffset = decoder.offset
|
||||
const chunkType = decoder.readByte()
|
||||
const chunkLength = decoder.readUint53()
|
||||
const chunkData = new Decoder(decoder.readRawBytes(chunkLength))
|
||||
const hash = new Hash()
|
||||
hash.update(decoder.buf.subarray(hashStartOffset, decoder.offset))
|
||||
if (!compareBytes(hash.digest(), expectedHash)) {
|
||||
throw new RangeError('Hash does not match data')
|
||||
}
|
||||
if (chunkType === 0) {
|
||||
// decode document
|
||||
} else if (chunkType === 1) {
|
||||
return decodeChangeHeader(chunkData)
|
||||
} else {
|
||||
console.log(`Warning: ignoring chunk with unknown type ${chunkType}`)
|
||||
}
|
||||
}
|
||||
|
||||
function encodeChange(changeObj) {
|
||||
const { change, actorIds } = parseAllOpIds(changeObj)
|
||||
const columns = encodeOps(change.ops)
|
||||
const columnIds = Object.entries(CHANGE_COLUMNS).sort((a, b) => a[1] - b[1])
|
||||
|
||||
return encodeContainerHeader('change', encoder => {
|
||||
encodeChangeHeader(encoder, change, actorIds)
|
||||
for (let [columnName, columnId] of columnIds) {
|
||||
if (columns[columnName] && !columns[columnName].onlyNulls) {
|
||||
const buffer = columns[columnName].buffer
|
||||
if (buffer.byteLength > 0) {
|
||||
encoder.appendUint53(columnId)
|
||||
encoder.appendPrefixedBytes(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function decodeChange(buffer) {
|
||||
const decoder = new Decoder(buffer), changes = []
|
||||
do {
|
||||
const change = decodeContainerHeader(decoder)
|
||||
if (change) changes.push(change)
|
||||
} while (!decoder.done)
|
||||
return changes
|
||||
}
|
||||
|
||||
module.exports = { encodeChange, decodeChange }
|
|
@ -1,47 +0,0 @@
|
|||
const ROOT_ID = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
function isObject(obj) {
|
||||
return typeof obj === 'object' && obj !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of the object `obj`. Faster than `Object.assign({}, obj)`.
|
||||
* https://jsperf.com/cloning-large-objects/1
|
||||
*/
|
||||
function copyObject(obj) {
|
||||
if (!isObject(obj)) return {}
|
||||
let copy = {}
|
||||
for (let key of Object.keys(obj)) {
|
||||
copy[key] = obj[key]
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all components of `clock1` are less than or equal to those
|
||||
* of `clock2` (both clocks given as Immutable.js Map objects). Returns false
|
||||
* if there is at least one component in which `clock1` is greater than
|
||||
* `clock2` (that is, either `clock1` is overall greater than `clock2`, or the
|
||||
* clocks are incomparable).
|
||||
*/
|
||||
function lessOrEqual(clock1, clock2) {
|
||||
return clock1.keySeq().concat(clock2.keySeq()).reduce(
|
||||
(result, key) => (result && clock1.get(key, 0) <= clock2.get(key, 0)),
|
||||
true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a string in the form that is used to identify operations (a counter concatenated
|
||||
* with an actor ID, separated by an `@` sign) and returns an object `{counter, actorId}`.
|
||||
*/
|
||||
function parseOpId(opId) {
|
||||
const match = /^(\d+)@(.*)$/.exec(opId || '')
|
||||
if (!match) {
|
||||
throw new RangeError(`Not a valid opId: ${opId}`)
|
||||
}
|
||||
return {counter: parseInt(match[1]), actorId: match[2]}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ROOT_ID, isObject, copyObject, lessOrEqual, parseOpId
|
||||
}
|
|
@ -1,759 +0,0 @@
|
|||
/**
|
||||
* UTF-8 decoding and encoding
|
||||
*/
|
||||
let stringToUtf8, utf8ToString
|
||||
|
||||
if (typeof TextEncoder === 'function' && typeof TextDecoder === 'function') {
|
||||
// Modern web browsers:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encode
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
|
||||
const utf8encoder = new TextEncoder(), utf8decoder = new TextDecoder('utf-8')
|
||||
stringToUtf8 = (string) => utf8encoder.encode(string)
|
||||
utf8ToString = (buffer) => utf8decoder.decode(buffer)
|
||||
|
||||
} else if (typeof Buffer === 'function') {
|
||||
// Node.js:
|
||||
// https://nodejs.org/api/buffer.html
|
||||
// https://nodejs.org/api/string_decoder.html
|
||||
const { StringDecoder } = require('string_decoder')
|
||||
const utf8decoder = new StringDecoder('utf8')
|
||||
stringToUtf8 = (string) => Buffer.from(string, 'utf8')
|
||||
// In Node >= 10 we can simply do "utf8decoder.end(buffer)". However, in Node 8 there
|
||||
// is a bug that causes an Uint8Array to be incorrectly decoded when passed directly to
|
||||
// StringDecoder.end(). Wrapping in an additional "Buffer.from()" works around this bug.
|
||||
utf8ToString = (buffer) => utf8decoder.end(Buffer.from(buffer))
|
||||
|
||||
} else {
|
||||
// Could use a polyfill? e.g. https://github.com/anonyco/FastestSmallestTextEncoderDecoder
|
||||
throw new Error('Platform does not provide UTF-8 encoding/decoding feature')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper around an Uint8Array that allows values to be appended to the buffer,
|
||||
* and that automatically grows the buffer when space runs out.
|
||||
*/
|
||||
class Encoder {
|
||||
constructor() {
|
||||
this.buf = new Uint8Array(16)
|
||||
this.offset = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte array containing the encoded data.
|
||||
*/
|
||||
get buffer() {
|
||||
this.finish()
|
||||
return this.buf.subarray(0, this.offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocates the encoder's buffer to be bigger.
|
||||
*/
|
||||
grow(minSize = 0) {
|
||||
let newSize = this.buf.byteLength * 4
|
||||
while (newSize < minSize) newSize *= 2
|
||||
const newBuf = new Uint8Array(newSize)
|
||||
newBuf.set(this.buf, 0)
|
||||
this.buf = newBuf
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends one byte (0 to 255) to the buffer.
|
||||
*/
|
||||
appendByte(value) {
|
||||
if (this.offset >= this.buf.byteLength) this.grow()
|
||||
this.buf[this.offset] = value
|
||||
this.offset += 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a 32-bit nonnegative integer in a variable number of bytes using
|
||||
* the LEB128 encoding scheme (https://en.wikipedia.org/wiki/LEB128) and
|
||||
* appends it to the buffer. Returns the number of bytes written.
|
||||
*/
|
||||
appendUint32(value) {
|
||||
if (!Number.isInteger(value)) throw new RangeError('value is not an integer')
|
||||
if (value < 0 || value > 0xffffffff) throw new RangeError('number out of range')
|
||||
|
||||
const numBytes = Math.max(1, Math.ceil((32 - Math.clz32(value)) / 7))
|
||||
if (this.offset + numBytes > this.buf.byteLength) this.grow()
|
||||
|
||||
for (let i = 0; i < numBytes; i++) {
|
||||
this.buf[this.offset + i] = (value & 0x7f) | (i === numBytes - 1 ? 0x00 : 0x80)
|
||||
value >>>= 7 // zero-filling right shift
|
||||
}
|
||||
this.offset += numBytes
|
||||
return numBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a 32-bit signed integer in a variable number of bytes using the
|
||||
* LEB128 encoding scheme (https://en.wikipedia.org/wiki/LEB128) and appends
|
||||
* it to the buffer. Returns the number of bytes written.
|
||||
*/
|
||||
appendInt32(value) {
|
||||
if (!Number.isInteger(value)) throw new RangeError('value is not an integer')
|
||||
if (value < -0x80000000 || value > 0x7fffffff) throw new RangeError('number out of range')
|
||||
|
||||
const numBytes = Math.ceil((33 - Math.clz32(value >= 0 ? value : -value - 1)) / 7)
|
||||
if (this.offset + numBytes > this.buf.byteLength) this.grow()
|
||||
|
||||
for (let i = 0; i < numBytes; i++) {
|
||||
this.buf[this.offset + i] = (value & 0x7f) | (i === numBytes - 1 ? 0x00 : 0x80)
|
||||
value >>= 7 // sign-propagating right shift
|
||||
}
|
||||
this.offset += numBytes
|
||||
return numBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a nonnegative integer in a variable number of bytes using the LEB128
|
||||
* encoding scheme, up to the maximum size of integers supported by JavaScript
|
||||
* (53 bits).
|
||||
*/
|
||||
appendUint53(value) {
|
||||
if (!Number.isInteger(value)) throw new RangeError('value is not an integer')
|
||||
if (value < 0 || value > Number.MAX_SAFE_INTEGER) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
const high32 = Math.floor(value / 0x100000000)
|
||||
const low32 = (value & 0xffffffff) >>> 0 // right shift to interpret as unsigned
|
||||
return this.appendUint64(high32, low32)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a signed integer in a variable number of bytes using the LEB128
|
||||
* encoding scheme, up to the maximum size of integers supported by JavaScript
|
||||
* (53 bits).
|
||||
*/
|
||||
appendInt53(value) {
|
||||
if (!Number.isInteger(value)) throw new RangeError('value is not an integer')
|
||||
if (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
const high32 = Math.floor(value / 0x100000000)
|
||||
const low32 = (value & 0xffffffff) >>> 0 // right shift to interpret as unsigned
|
||||
return this.appendInt64(high32, low32)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a 64-bit nonnegative integer in a variable number of bytes using
|
||||
* the LEB128 encoding scheme, and appends it to the buffer. The number is
|
||||
* given as two 32-bit halves since JavaScript cannot accurately represent
|
||||
* integers with more than 53 bits in a single variable.
|
||||
*/
|
||||
appendUint64(high32, low32) {
|
||||
if (!Number.isInteger(high32) || !Number.isInteger(low32)) {
|
||||
throw new RangeError('value is not an integer')
|
||||
}
|
||||
if (high32 < 0 || high32 > 0xffffffff || low32 < 0 || low32 > 0xffffffff) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
if (high32 === 0) return this.appendUint32(low32)
|
||||
|
||||
const numBytes = Math.ceil((64 - Math.clz32(high32)) / 7)
|
||||
if (this.offset + numBytes > this.buf.byteLength) this.grow()
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.buf[this.offset + i] = (low32 & 0x7f) | 0x80
|
||||
low32 >>>= 7 // zero-filling right shift
|
||||
}
|
||||
this.buf[this.offset + 4] = (low32 & 0x0f) | ((high32 & 0x07) << 4) | (numBytes === 5 ? 0x00 : 0x80)
|
||||
high32 >>>= 3
|
||||
for (let i = 5; i < numBytes; i++) {
|
||||
this.buf[this.offset + i] = (high32 & 0x7f) | (i === numBytes - 1 ? 0x00 : 0x80)
|
||||
high32 >>>= 7
|
||||
}
|
||||
this.offset += numBytes
|
||||
return numBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a 64-bit signed integer in a variable number of bytes using the
|
||||
* LEB128 encoding scheme, and appends it to the buffer. The number is given
|
||||
* as two 32-bit halves since JavaScript cannot accurately represent integers
|
||||
* with more than 53 bits in a single variable. The sign of the 64-bit
|
||||
* number is determined by the sign of the `high32` half; the sign of the
|
||||
* `low32` half is ignored.
|
||||
*/
|
||||
appendInt64(high32, low32) {
|
||||
if (!Number.isInteger(high32) || !Number.isInteger(low32)) {
|
||||
throw new RangeError('value is not an integer')
|
||||
}
|
||||
if (high32 < -0x80000000 || high32 > 0x7fffffff || low32 < -0x80000000 || low32 > 0xffffffff) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
low32 >>>= 0 // interpret as unsigned
|
||||
if (high32 === 0 && low32 <= 0x7fffffff) return this.appendInt32(low32)
|
||||
if (high32 === -1 && low32 >= 0x80000000) return this.appendInt32(low32 - 0x100000000)
|
||||
|
||||
const numBytes = Math.ceil((65 - Math.clz32(high32 >= 0 ? high32 : -high32 - 1)) / 7)
|
||||
if (this.offset + numBytes > this.buf.byteLength) this.grow()
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.buf[this.offset + i] = (low32 & 0x7f) | 0x80
|
||||
low32 >>>= 7 // zero-filling right shift
|
||||
}
|
||||
this.buf[this.offset + 4] = (low32 & 0x0f) | ((high32 & 0x07) << 4) | (numBytes === 5 ? 0x00 : 0x80)
|
||||
high32 >>= 3 // sign-propagating right shift
|
||||
for (let i = 5; i < numBytes; i++) {
|
||||
this.buf[this.offset + i] = (high32 & 0x7f) | (i === numBytes - 1 ? 0x00 : 0x80)
|
||||
high32 >>= 7
|
||||
}
|
||||
this.offset += numBytes
|
||||
return numBytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the contents of byte buffer `data` to the buffer. Returns the
|
||||
* number of bytes appended.
|
||||
*/
|
||||
appendRawBytes(data) {
|
||||
if (this.offset + data.byteLength > this.buf.byteLength) {
|
||||
this.grow(this.offset + data.byteLength)
|
||||
}
|
||||
this.buf.set(data, this.offset)
|
||||
this.offset += data.byteLength
|
||||
return data.byteLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a UTF-8 string to the buffer, without any metadata. Returns the
|
||||
* number of bytes appended.
|
||||
*/
|
||||
appendRawString(value) {
|
||||
if (typeof value !== 'string') throw new TypeError('value is not a string')
|
||||
return this.appendRawBytes(stringToUtf8(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the contents of byte buffer `data` to the buffer, prefixed with the
|
||||
* number of bytes in the buffer (as a LEB128-encoded unsigned integer).
|
||||
*/
|
||||
appendPrefixedBytes(data) {
|
||||
this.appendUint53(data.byteLength)
|
||||
this.appendRawBytes(data)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a UTF-8 string to the buffer, prefixed with its length in bytes
|
||||
* (where the length is encoded as an unsigned LEB128 integer).
|
||||
*/
|
||||
appendPrefixedString(value) {
|
||||
if (typeof value !== 'string') throw new TypeError('value is not a string')
|
||||
this.appendPrefixedBytes(stringToUtf8(value))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes any unwritten data to the buffer. Call this before reading from
|
||||
* the buffer constructed by this Encoder.
|
||||
*/
|
||||
finish() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterpart to Encoder. Wraps a Uint8Array buffer with a cursor indicating
|
||||
* the current decoding position, and allows values to be incrementally read by
|
||||
* decoding the bytes at the current position.
|
||||
*/
|
||||
class Decoder {
|
||||
constructor(buffer) {
|
||||
this.buf = buffer
|
||||
this.offset = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if there is still data to be read at the current decoding
|
||||
* position, and true if we are at the end of the buffer.
|
||||
*/
|
||||
get done() {
|
||||
return this.offset === this.buf.byteLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one byte (0 to 255) from the buffer.
|
||||
*/
|
||||
readByte() {
|
||||
this.offset += 1
|
||||
return this.buf[this.offset - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a LEB128-encoded unsigned integer from the current position in the buffer.
|
||||
* Throws an exception if the value doesn't fit in a 32-bit unsigned int.
|
||||
*/
|
||||
readUint32() {
|
||||
let result = 0, shift = 0
|
||||
while (this.offset < this.buf.byteLength) {
|
||||
const nextByte = this.buf[this.offset]
|
||||
if (shift === 28 && (nextByte & 0xf0) !== 0) { // more than 5 bytes, or value > 0xffffffff
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
result = (result | (nextByte & 0x7f) << shift) >>> 0 // right shift to interpret value as unsigned
|
||||
shift += 7
|
||||
this.offset++
|
||||
if ((nextByte & 0x80) === 0) return result
|
||||
}
|
||||
throw new RangeError('buffer ended with incomplete number')
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a LEB128-encoded signed integer from the current position in the buffer.
|
||||
* Throws an exception if the value doesn't fit in a 32-bit signed int.
|
||||
*/
|
||||
readInt32() {
|
||||
let result = 0, shift = 0
|
||||
while (this.offset < this.buf.byteLength) {
|
||||
const nextByte = this.buf[this.offset]
|
||||
if ((shift === 28 && (nextByte & 0x80) !== 0) || // more than 5 bytes
|
||||
(shift === 28 && (nextByte & 0x40) === 0 && (nextByte & 0x38) !== 0) || // positive int > 0x7fffffff
|
||||
(shift === 28 && (nextByte & 0x40) !== 0 && (nextByte & 0x38) !== 0x38)) { // negative int < -0x80000000
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
result |= (nextByte & 0x7f) << shift
|
||||
shift += 7
|
||||
this.offset++
|
||||
|
||||
if ((nextByte & 0x80) === 0) {
|
||||
if ((nextByte & 0x40) === 0 || shift > 28) {
|
||||
return result // positive, or negative value that doesn't need sign-extending
|
||||
} else {
|
||||
return result | (-1 << shift) // sign-extend negative integer
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RangeError('buffer ended with incomplete number')
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a LEB128-encoded unsigned integer from the current position in the
|
||||
* buffer. Allows any integer that can be safely represented by JavaScript
|
||||
* (up to 2^53 - 1), and throws an exception outside of that range.
|
||||
*/
|
||||
readUint53() {
|
||||
const { low32, high32 } = this.readUint64()
|
||||
if (high32 < 0 || high32 > 0x1fffff) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
return high32 * 0x100000000 + low32
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a LEB128-encoded signed integer from the current position in the
|
||||
* buffer. Allows any integer that can be safely represented by JavaScript
|
||||
* (between -(2^53 - 1) and 2^53 - 1), throws an exception outside of that range.
|
||||
*/
|
||||
readInt53() {
|
||||
const { low32, high32 } = this.readInt64()
|
||||
if (high32 < -0x200000 || (high32 === -0x200000 && low32 === 0) || high32 > 0x1fffff) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
return high32 * 0x100000000 + low32
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a LEB128-encoded unsigned integer from the current position in the
|
||||
* buffer. Throws an exception if the value doesn't fit in a 64-bit unsigned
|
||||
* int. Returns the number in two 32-bit halves, as an object of the form
|
||||
* `{high32, low32}`.
|
||||
*/
|
||||
readUint64() {
|
||||
let low32 = 0, high32 = 0, shift = 0
|
||||
while (this.offset < this.buf.byteLength && shift <= 28) {
|
||||
const nextByte = this.buf[this.offset]
|
||||
low32 = (low32 | (nextByte & 0x7f) << shift) >>> 0 // right shift to interpret value as unsigned
|
||||
if (shift === 28) {
|
||||
high32 = (nextByte & 0x70) >>> 4
|
||||
}
|
||||
shift += 7
|
||||
this.offset++
|
||||
if ((nextByte & 0x80) === 0) return { high32, low32 }
|
||||
}
|
||||
|
||||
shift = 3
|
||||
while (this.offset < this.buf.byteLength) {
|
||||
const nextByte = this.buf[this.offset]
|
||||
if (shift === 31 && (nextByte & 0xfe) !== 0) { // more than 10 bytes, or value > 2^64 - 1
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
high32 = (high32 | (nextByte & 0x7f) << shift) >>> 0
|
||||
shift += 7
|
||||
this.offset++
|
||||
if ((nextByte & 0x80) === 0) return { high32, low32 }
|
||||
}
|
||||
throw new RangeError('buffer ended with incomplete number')
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a LEB128-encoded signed integer from the current position in the
|
||||
* buffer. Throws an exception if the value doesn't fit in a 64-bit signed
|
||||
* int. Returns the number in two 32-bit halves, as an object of the form
|
||||
* `{high32, low32}`. The `low32` half is always non-negative, and the
|
||||
* sign of the `high32` half indicates the sign of the 64-bit number.
|
||||
*/
|
||||
readInt64() {
|
||||
let low32 = 0, high32 = 0, shift = 0
|
||||
while (this.offset < this.buf.byteLength && shift <= 28) {
|
||||
const nextByte = this.buf[this.offset]
|
||||
low32 = (low32 | (nextByte & 0x7f) << shift) >>> 0 // right shift to interpret value as unsigned
|
||||
if (shift === 28) {
|
||||
high32 = (nextByte & 0x70) >>> 4
|
||||
}
|
||||
shift += 7
|
||||
this.offset++
|
||||
if ((nextByte & 0x80) === 0) {
|
||||
if ((nextByte & 0x40) !== 0) { // sign-extend negative integer
|
||||
if (shift < 32) low32 = (low32 | (-1 << shift)) >>> 0
|
||||
high32 |= -1 << Math.max(shift - 32, 0)
|
||||
}
|
||||
return { high32, low32 }
|
||||
}
|
||||
}
|
||||
|
||||
shift = 3
|
||||
while (this.offset < this.buf.byteLength) {
|
||||
const nextByte = this.buf[this.offset]
|
||||
// On the 10th byte there are only two valid values: all 7 value bits zero
|
||||
// (if the value is positive) or all 7 bits one (if the value is negative)
|
||||
if (shift === 31 && nextByte !== 0 && nextByte !== 0x7f) {
|
||||
throw new RangeError('number out of range')
|
||||
}
|
||||
high32 |= (nextByte & 0x7f) << shift
|
||||
shift += 7
|
||||
this.offset++
|
||||
if ((nextByte & 0x80) === 0) {
|
||||
if ((nextByte & 0x40) !== 0 && shift < 32) { // sign-extend negative integer
|
||||
high32 |= -1 << shift
|
||||
}
|
||||
return { high32, low32 }
|
||||
}
|
||||
}
|
||||
throw new RangeError('buffer ended with incomplete number')
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a subarray `length` bytes in size, starting from the current
|
||||
* position in the buffer, and moves the position forward.
|
||||
*/
|
||||
readRawBytes(length) {
|
||||
const start = this.offset
|
||||
if (start + length > this.buf.byteLength) {
|
||||
throw new RangeError('subarray exceeds buffer size')
|
||||
}
|
||||
this.offset += length
|
||||
return this.buf.subarray(start, this.offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts `length` bytes from the buffer, starting from the current position,
|
||||
* and returns the UTF-8 string decoding of those bytes.
|
||||
*/
|
||||
readRawString(length) {
|
||||
return utf8ToString(this.readRawBytes(length))
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a subarray from the current position in the buffer, prefixed with
|
||||
* its length in bytes (encoded as an unsigned LEB128 integer).
|
||||
*/
|
||||
readPrefixedBytes() {
|
||||
return this.readRawBytes(this.readUint53())
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a UTF-8 string from the current position in the buffer, prefixed with its
|
||||
* length in bytes (where the length is encoded as an unsigned LEB128 integer).
|
||||
*/
|
||||
readPrefixedString() {
|
||||
return utf8ToString(this.readPrefixedBytes())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An encoder that uses run-length encoding to compress sequences of repeated
|
||||
* values. The constructor argument specifies the type of values, which may be
|
||||
* either 'int', 'uint', or 'utf8'. Besides valid values of the selected
|
||||
* datatype, values may also be null.
|
||||
*
|
||||
* The encoded buffer starts with a LEB128-encoded signed integer, the
|
||||
* repetition count. The interpretation of the following values depends on this
|
||||
* repetition count:
|
||||
* - If this number is a positive value n, the next value in the buffer
|
||||
* (encoded as the specified datatype) is repeated n times in the sequence.
|
||||
* - If the repetition count is a negative value -n, then the next n values
|
||||
* (encoded as the specified datatype) in the buffer are treated as a
|
||||
* literal, i.e. they appear in the sequence without any further
|
||||
* interpretation or repetition.
|
||||
* - If the repetition count is zero, then the next value in the buffer is a
|
||||
* LEB128-encoded unsigned integer indicating the number of null values
|
||||
* that appear at the current position in the sequence.
|
||||
*
|
||||
* After one of these three has completed, the process repeats, starting again
|
||||
* with a repetition count, until we reach the end of the buffer.
|
||||
*/
|
||||
class RLEEncoder extends Encoder {
|
||||
constructor(type) {
|
||||
super()
|
||||
this.type = type
|
||||
this.lastValue = undefined
|
||||
this.count = 0
|
||||
this.literal = []
|
||||
this.onlyNulls = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a new value to the sequence.
|
||||
*/
|
||||
appendValue(value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
this.onlyNulls = false
|
||||
}
|
||||
if (this.lastValue === undefined) {
|
||||
this.lastValue = value
|
||||
}
|
||||
if (this.lastValue === value) {
|
||||
this.count += 1
|
||||
return
|
||||
}
|
||||
if (this.lastValue !== null && this.count === 1) {
|
||||
this.literal.push(this.lastValue)
|
||||
this.lastValue = value
|
||||
this.count = 0
|
||||
}
|
||||
|
||||
if ((value === null || value === undefined || this.count > 1) && this.literal.length > 0) {
|
||||
this.appendInt53(-this.literal.length)
|
||||
for (let v of this.literal) this.appendRawValue(v)
|
||||
this.literal = []
|
||||
}
|
||||
|
||||
if (this.lastValue === null && this.count > 0) {
|
||||
this.appendInt32(0)
|
||||
this.appendUint53(this.count)
|
||||
} else if (this.count > 1) {
|
||||
this.appendInt53(this.count)
|
||||
this.appendRawValue(this.lastValue)
|
||||
}
|
||||
this.lastValue = value
|
||||
this.count = (value === undefined ? 0 : 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method, do not call from outside the class.
|
||||
*/
|
||||
appendRawValue(value) {
|
||||
if (this.type === 'int') {
|
||||
this.appendInt53(value)
|
||||
} else if (this.type === 'uint') {
|
||||
this.appendUint53(value)
|
||||
} else if (this.type === 'utf8') {
|
||||
this.appendPrefixedString(value)
|
||||
} else {
|
||||
throw new RangeError(`Unknown RLEEncoder datatype: ${this.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes any unwritten data to the buffer. Call this before reading from
|
||||
* the buffer constructed by this Encoder.
|
||||
*/
|
||||
finish() {
|
||||
this.appendValue(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterpart to RLEEncoder: reads values from an RLE-compressed sequence,
|
||||
* returning nulls and repeated values as required.
|
||||
*/
|
||||
class RLEDecoder extends Decoder {
|
||||
constructor(type, buffer) {
|
||||
super(buffer)
|
||||
this.type = type
|
||||
this.lastValue = undefined
|
||||
this.count = 0
|
||||
this.literal = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if there is still data to be read at the current decoding
|
||||
* position, and true if we are at the end of the buffer.
|
||||
*/
|
||||
get done() {
|
||||
return (this.count === 0) && (this.offset === this.buf.byteLength)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next value (or null) in the sequence.
|
||||
*/
|
||||
readValue() {
|
||||
if (this.done) return null
|
||||
|
||||
if (this.count === 0) {
|
||||
this.count = this.readInt53()
|
||||
if (this.count > 0) {
|
||||
this.lastValue = this.readRawValue()
|
||||
this.literal = false
|
||||
} else if (this.count < 0) {
|
||||
this.count = -this.count
|
||||
this.literal = true
|
||||
} else { // this.count == 0
|
||||
this.count = this.readUint53()
|
||||
this.lastValue = null
|
||||
this.literal = false
|
||||
}
|
||||
}
|
||||
|
||||
this.count -= 1
|
||||
if (this.literal) {
|
||||
return this.readRawValue()
|
||||
} else {
|
||||
return this.lastValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method, do not call from outside the class.
|
||||
*/
|
||||
readRawValue() {
|
||||
if (this.type === 'int') {
|
||||
return this.readInt53()
|
||||
} else if (this.type === 'uint') {
|
||||
return this.readUint53()
|
||||
} else if (this.type === 'utf8') {
|
||||
return this.readPrefixedString()
|
||||
} else {
|
||||
throw new RangeError(`Unknown RLEDecoder datatype: ${this.type}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of RLEEncoder: rather than storing the actual values passed to
|
||||
* appendValue(), this version stores only the first value, and for all
|
||||
* subsequent values it stores the difference to the previous value. This
|
||||
* encoding is good when values tend to come in sequentially incrementing runs,
|
||||
* because the delta between successive values is 1, and repeated values of 1
|
||||
* are easily compressed with run-length encoding.
|
||||
*
|
||||
* Null values are also allowed, as with RLEEncoder.
|
||||
*/
|
||||
class DeltaEncoder extends RLEEncoder {
|
||||
constructor() {
|
||||
super('int')
|
||||
this.absoluteValue = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a new integer value to the sequence.
|
||||
*/
|
||||
appendValue(value) {
|
||||
if (typeof value === 'number') {
|
||||
super.appendValue(value - this.absoluteValue)
|
||||
this.absoluteValue = value
|
||||
} else {
|
||||
super.appendValue(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterpart to DeltaEncoder: reads values from a delta-compressed sequence of
|
||||
* numbers (may include null values).
|
||||
*/
|
||||
class DeltaDecoder extends RLEDecoder {
|
||||
constructor(buffer) {
|
||||
super('int', buffer)
|
||||
this.absoluteValue = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next integer (or null) value in the sequence.
|
||||
*/
|
||||
readValue() {
|
||||
const value = super.readValue()
|
||||
if (value === null) return null
|
||||
this.absoluteValue += value
|
||||
return this.absoluteValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a sequence of boolean values by mapping it to a sequence of integers:
|
||||
* the number of false values, followed by the number of true values, followed
|
||||
* by the number of false values, and so on. Each number is encoded as a LEB128
|
||||
* unsigned integer. This encoding is a bit like RLEEncoder, except that we
|
||||
* only encode the repetition count but not the actual value, since the values
|
||||
* just alternate between false and true (starting with false).
|
||||
*/
|
||||
class BooleanEncoder extends Encoder {
|
||||
constructor() {
|
||||
super()
|
||||
this.lastValue = false
|
||||
this.count = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a new value to the sequence.
|
||||
*/
|
||||
appendValue(value) {
|
||||
if (value !== false && value !== true) {
|
||||
throw new RangeError(`Unsupported value for BooleanEncoder: ${value}`)
|
||||
}
|
||||
if (this.lastValue === value) {
|
||||
this.count += 1
|
||||
} else {
|
||||
this.appendUint53(this.count)
|
||||
this.lastValue = value
|
||||
this.count = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes any unwritten data to the buffer. Call this before reading from
|
||||
* the buffer constructed by this Encoder.
|
||||
*/
|
||||
finish() {
|
||||
if (this.count > 0) {
|
||||
this.appendUint53(this.count)
|
||||
this.count = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterpart to BooleanEncoder: reads boolean values from a runlength-encoded
|
||||
* sequence.
|
||||
*/
|
||||
class BooleanDecoder extends Decoder {
|
||||
constructor(buffer) {
|
||||
super(buffer)
|
||||
this.lastValue = true // is negated the first time we read a count
|
||||
this.count = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if there is still data to be read at the current decoding
|
||||
* position, and true if we are at the end of the buffer.
|
||||
*/
|
||||
get done() {
|
||||
return (this.count === 0) && (this.offset === this.buf.byteLength)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next value (or null) in the sequence.
|
||||
*/
|
||||
readValue() {
|
||||
while (this.count === 0) {
|
||||
this.count = this.readUint53()
|
||||
this.lastValue = !this.lastValue
|
||||
}
|
||||
this.count -= 1
|
||||
return this.lastValue
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Encoder, Decoder, RLEEncoder, RLEDecoder, DeltaEncoder, DeltaDecoder, BooleanEncoder, BooleanDecoder }
|
|
@ -1,7 +1,6 @@
|
|||
let Backend = require("./pkg")
|
||||
let util = require('util')
|
||||
|
||||
const { encodeChange, decodeChange } = require('./columnar')
|
||||
const { encodeChange, decodeChange } = require('automerge/backend/columnar')
|
||||
|
||||
function decodeChanges(binaryChanges) {
|
||||
let decoded = []
|
||||
|
@ -11,7 +10,6 @@ function decodeChanges(binaryChanges) {
|
|||
}
|
||||
for (let change of decodeChange(binaryChange)) decoded.push(change)
|
||||
}
|
||||
//console.log("CHANGES",util.inspect(decoded, {showHidden: false, depth: null}))
|
||||
return decoded
|
||||
}
|
||||
|
||||
|
@ -36,7 +34,6 @@ let mutate = (oldBackend,fn) => {
|
|||
let result = fn(state)
|
||||
oldBackend.frozen = true
|
||||
let newBackend = { state, clock: state.getClock(), frozen: false };
|
||||
//console.log("PATCH",util.inspect(result, {showHidden: false, depth: null}));
|
||||
return [ newBackend, result ]
|
||||
}
|
||||
|
||||
|
@ -50,7 +47,6 @@ let loadChanges = (backend,changes) => {
|
|||
}
|
||||
|
||||
let applyLocalChange = (backend,request) => {
|
||||
//console.log("LOCAL REQUEST",util.inspect(request, {showHidden: false, depth: null}))
|
||||
return mutate(backend, (b) => b.applyLocalChange(request));
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,8 @@
|
|||
"test": "cargo test && wasm-pack test --node"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-sha256": "^1.3.0",
|
||||
"immutable": "^3.8.2"
|
||||
"automerge": "automerge/automerge#performance",
|
||||
"fast-sha256": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"automerge": "^0.12.1",
|
||||
"mocha": "^6.2.0",
|
||||
"rimraf": "^2.6.3"
|
||||
}
|
||||
"devDependencies": { }
|
||||
}
|
||||
|
|
|
@ -2,566 +2,26 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
ansi-colors@3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
|
||||
integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
|
||||
|
||||
ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||
|
||||
ansi-regex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
automerge@^0.12.1:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/automerge/-/automerge-0.12.1.tgz#8e8ca23affa888c6376ee19068eab573cfa8ba09"
|
||||
integrity sha512-7JOiRk4b6EP/Uj0AjmZTeYICXJmBRHFkL0U3mlTNXuDlUr3c4v/Wb8v0RXiX4UuVgGjkovcjOdiBMkVmzdu2KQ==
|
||||
automerge@automerge/automerge#performance:
|
||||
version "0.14.0"
|
||||
resolved "https://codeload.github.com/automerge/automerge/tar.gz/ff12d30a10f593a88b58cf18d9a6e6290359d5d1"
|
||||
dependencies:
|
||||
fast-sha256 "^1.3.0"
|
||||
immutable "^3.8.2"
|
||||
transit-immutable-js "^0.7.0"
|
||||
transit-js "^0.8.861"
|
||||
uuid "3.3.2"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
browser-stdout@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
|
||||
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
|
||||
|
||||
camelcase@^5.0.0:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
chalk@^2.0.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
cliui@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
|
||||
integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
|
||||
dependencies:
|
||||
string-width "^3.1.0"
|
||||
strip-ansi "^5.2.0"
|
||||
wrap-ansi "^5.1.0"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
debug@3.2.6:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||
|
||||
define-properties@^1.1.2, define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||
dependencies:
|
||||
object-keys "^1.0.12"
|
||||
|
||||
diff@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
|
||||
|
||||
es-abstract@^1.17.0-next.1:
|
||||
version "1.17.4"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
|
||||
integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==
|
||||
dependencies:
|
||||
es-to-primitive "^1.2.1"
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.1"
|
||||
is-callable "^1.1.5"
|
||||
is-regex "^1.0.5"
|
||||
object-inspect "^1.7.0"
|
||||
object-keys "^1.1.1"
|
||||
object.assign "^4.1.0"
|
||||
string.prototype.trimleft "^2.1.1"
|
||||
string.prototype.trimright "^2.1.1"
|
||||
|
||||
es-to-primitive@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
|
||||
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
|
||||
dependencies:
|
||||
is-callable "^1.1.4"
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
esprima@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
uuid "^3.4.0"
|
||||
|
||||
fast-sha256@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6"
|
||||
integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==
|
||||
|
||||
find-up@3.0.0, find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
flat@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2"
|
||||
integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==
|
||||
dependencies:
|
||||
is-buffer "~2.0.3"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
get-caller-file@^2.0.1:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
glob@7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.1.3:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
growl@1.10.5:
|
||||
version "1.10.5"
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
|
||||
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-symbols@^1.0.0, has-symbols@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
|
||||
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
he@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
immutable@^3.8.2:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
||||
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
is-buffer@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
|
||||
integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
|
||||
|
||||
is-callable@^1.1.4, is-callable@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
|
||||
integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
|
||||
|
||||
is-date-object@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
|
||||
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
|
||||
|
||||
is-fullwidth-code-point@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
|
||||
|
||||
is-regex@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae"
|
||||
integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-symbol@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
|
||||
integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
|
||||
dependencies:
|
||||
has-symbols "^1.0.1"
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||
|
||||
js-yaml@3.13.1:
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
|
||||
dependencies:
|
||||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
log-symbols@2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
|
||||
integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
|
||||
minimatch@3.0.4, minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
mkdirp@0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
mocha@^6.2.0:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.2.tgz#5d8987e28940caf8957a7d7664b910dc5b2fea20"
|
||||
integrity sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==
|
||||
dependencies:
|
||||
ansi-colors "3.2.3"
|
||||
browser-stdout "1.3.1"
|
||||
debug "3.2.6"
|
||||
diff "3.5.0"
|
||||
escape-string-regexp "1.0.5"
|
||||
find-up "3.0.0"
|
||||
glob "7.1.3"
|
||||
growl "1.10.5"
|
||||
he "1.2.0"
|
||||
js-yaml "3.13.1"
|
||||
log-symbols "2.2.0"
|
||||
minimatch "3.0.4"
|
||||
mkdirp "0.5.1"
|
||||
ms "2.1.1"
|
||||
node-environment-flags "1.0.5"
|
||||
object.assign "4.1.0"
|
||||
strip-json-comments "2.0.1"
|
||||
supports-color "6.0.0"
|
||||
which "1.3.1"
|
||||
wide-align "1.1.3"
|
||||
yargs "13.3.0"
|
||||
yargs-parser "13.1.1"
|
||||
yargs-unparser "1.6.0"
|
||||
|
||||
ms@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-environment-flags@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a"
|
||||
integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==
|
||||
dependencies:
|
||||
object.getownpropertydescriptors "^2.0.3"
|
||||
semver "^5.7.0"
|
||||
|
||||
object-inspect@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
|
||||
integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
|
||||
|
||||
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
||||
|
||||
object.assign@4.1.0, object.assign@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
|
||||
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
function-bind "^1.1.1"
|
||||
has-symbols "^1.0.0"
|
||||
object-keys "^1.0.11"
|
||||
|
||||
object.getownpropertydescriptors@^2.0.3:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
|
||||
integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.17.0-next.1"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
p-limit@^2.0.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
|
||||
integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-locate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
|
||||
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
|
||||
dependencies:
|
||||
p-limit "^2.0.0"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
rimraf@^2.6.3:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
semver@^5.7.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
"string-width@^1.0.2 || 2":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string-width@^3.0.0, string-width@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
|
||||
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
|
||||
dependencies:
|
||||
emoji-regex "^7.0.1"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string.prototype.trimleft@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74"
|
||||
integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
function-bind "^1.1.1"
|
||||
|
||||
string.prototype.trimright@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9"
|
||||
integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
function-bind "^1.1.1"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-json-comments@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
supports-color@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a"
|
||||
integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
transit-immutable-js@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/transit-immutable-js/-/transit-immutable-js-0.7.0.tgz#993e25089b6311ff402140f556276d6d253005d9"
|
||||
|
@ -572,78 +32,7 @@ transit-js@^0.8.861:
|
|||
resolved "https://registry.yarnpkg.com/transit-js/-/transit-js-0.8.861.tgz#829e516b80349a41fff5d59f5e6993b5473f72c9"
|
||||
integrity sha512-4O9OrYPZw6C0M5gMTvaeOp+xYz6EF79JsyxIvqXHlt+pisSrioJWFOE80N8aCPoJLcNaXF442RZrVtdmd4wkDQ==
|
||||
|
||||
uuid@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
which-module@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||
|
||||
which@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
||||
integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
wrap-ansi@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
|
||||
integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
string-width "^3.0.0"
|
||||
strip-ansi "^5.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
||||
|
||||
yargs-parser@13.1.1, yargs-parser@^13.1.1:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
|
||||
integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-unparser@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f"
|
||||
integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==
|
||||
dependencies:
|
||||
flat "^4.1.0"
|
||||
lodash "^4.17.15"
|
||||
yargs "^13.3.0"
|
||||
|
||||
yargs@13.3.0, yargs@^13.3.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
|
||||
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
|
||||
dependencies:
|
||||
cliui "^5.0.0"
|
||||
find-up "^3.0.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^3.0.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.1"
|
||||
uuid@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
|
|
@ -121,18 +121,14 @@ impl Backend {
|
|||
&self,
|
||||
diffs: Option<Diff>,
|
||||
request: Option<&ChangeRequest>,
|
||||
incremental: bool,
|
||||
_incremental: bool,
|
||||
) -> Result<Patch, AutomergeError> {
|
||||
Ok(Patch {
|
||||
version: self.versions.last().map(|v| v.version).unwrap_or(0),
|
||||
can_undo: self.can_undo(),
|
||||
can_redo: self.can_redo(),
|
||||
diffs,
|
||||
clock: if incremental {
|
||||
None
|
||||
} else {
|
||||
Some(self.clock.clone())
|
||||
},
|
||||
clock: self.clock.clone(),
|
||||
actor: request.map(|r| r.actor.clone()),
|
||||
seq: request.map(|r| r.seq),
|
||||
})
|
||||
|
|
|
@ -293,10 +293,9 @@ pub struct Patch {
|
|||
pub actor: Option<ActorID>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub seq: Option<u32>,
|
||||
pub clock: Clock,
|
||||
pub can_undo: bool,
|
||||
pub can_redo: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub clock: Option<Clock>,
|
||||
pub version: u64,
|
||||
#[serde(serialize_with = "Patch::top_level_serialize")]
|
||||
pub diffs: Option<Diff>,
|
||||
|
|
Loading…
Add table
Reference in a new issue