ottypes / json1

This is an operational transform type replacement for ottypes/json0
316 stars 27 forks source link

JSON1

Status: Usable in practice, but contains a couple super obscure known bugs. See below for details.

This is an operational transformation type for arbitrary JSON trees. It supports concurrently editing arbitrarily complex nested structures. Fancy features:

This code it is written to replace ottypes/json0. JSON1 implements a superset of JSON0's functionality.

The spec for operations is in spec.md.

Usage

The JSON library has 2 main APIs:

const json1 = require('ot-json1')

const op1 = json1.moveOp(['a', 'x'], ['a', 'y'])

// The easiest way to make compound operations is to just compose smaller operations
const op2 = [
  json1.moveOp(['a'], ['b']),
  json1.insertOp(['b', 'z'], 'hi there')
].reduce(json1.type.compose, null)

// op2 = [['a', {p:0}], ['b', {d:0}, 'x', {i: 'hi there'}]]

const op1_ = json1.type.transform(op1, op2, 'left')
// op1_ now moves b.x -> b.y instead, because op2 moved 'a' to 'b'.

let doc = {a: {x: 5}}
doc = json1.type.apply(doc, op2) // doc = {b: {x: 5, z: 'hi there'}}
doc = json1.type.apply(doc, op1_) // doc = {b: {y: 5, z: 'hi there'}}

// Using the CP1 diamond property, this is the same as:

doc = {a: {x: 5}}
doc = json1.type.apply(doc, op1) // doc = {a: {y: 5}}
const op2_ = json1.type.transform(op2, op1, 'right')
doc = json1.type.apply(doc, op2) // doc = {b: {y: 5, z: 'hi there'}}

Standard operation creation functions

These functions all return very simple operations. The easiest way to make more complex operations is to combine these pieces using compose. For example:

const op = [
  json1.insertOp([], {title: '', contents: '', public: false}),
  json1.editOp(['title'], 'text-unicode', ['My cool blog entry']),
  json1.replaceOp(['public', false, true])
].reduce(json1.type.compose, null)

Conflicts

TODO: Describe how this works and how conflict handling is configured

Interoperability with JSON0

This library supports a superset of the capabilities of JSON0, but the two types have some important differences:

You can convert JSON0 operations to JSON1 operations using json0-to-1. This is a work in progress and doesn't currently support converting string values. Please make noise & consider helping out if this conversion code is important to you. This conversion code guarantees that json1.apply(doc, convert(json0_op)) === json0.apply(doc, json0_op) but this invariant is not true through transform. json1.transform(convert(op1), convert(op2)) !== convert(json0.transform(op1, op2)) in some cases due to slightly different handling of conflicting list indexes.

Limitations

Your document must only contain pure JSON-stringifyable content. No dates, functions or self-references allowed. Your object should be identical to JSON.parse(JSON.stringify(obj)).

Note that this library is currently in preview release. In practical terms, this means:

License

Copyright (c) 2013-2018, Joseph Gentle <me@josephg.com>

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.