Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

automerge

Package Overview
Dependencies
Maintainers
2
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

automerge - npm Package Compare versions

Comparing version 0.6.0 to 0.7.0

CHANGELOG.md

2

package.json
{
"name": "automerge",
"version": "0.6.0",
"version": "0.7.0",
"description": "Data structures for building collaborative applications",

@@ -5,0 +5,0 @@ "main": "src/automerge.js",

@@ -52,3 +52,3 @@ # Automerge

peer-to-peer model using [WebRTC](https://webrtc.org/).
* **Immutable state**. A Automerge object is an immutable snapshot of the application state at one
* **Immutable state**. An Automerge object is an immutable snapshot of the application state at one
point in time. Whenever you make a change, or merge in a change that came from the network, you

@@ -158,3 +158,3 @@ get back a new state object reflecting that change. This fact makes Automerge compatible with the

// [ { title: 'Rewrite everything in Haskell', done: true },
// { title: 'Rewrite everything in Clojure', done: false } }
// { title: 'Rewrite everything in Clojure', done: false } ] }

@@ -489,3 +489,3 @@ // And, unbeknownst to device 1, also make a change on device 2:

* No integrity checking: if a buggy (or malicious) device makes corrupted edits, it can cause
the application state on other devices to be come corrupted or go out of sync.
the application state on other devices to become corrupted or go out of sync.
* No security: there is currently no encryption, authentication, or access control.

@@ -492,0 +492,0 @@ * Small number of collaborators: Automerge is designed for small-group collaborations. While there

@@ -5,10 +5,8 @@ const { Map, List, fromJS } = require('immutable')

const OpSet = require('./op_set')
const {isObject, checkTarget, makeChange, merge, applyChanges} = require('./auto_api')
const FreezeAPI = require('./freeze_api')
const ImmutableAPI = require('./immutable_api')
const { Text } = require('./text')
const transit = require('transit-immutable-js')
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function makeOp(state, opProps) {

@@ -116,11 +114,2 @@ const opSet = state.get('opSet'), actor = state.get('actorId'), op = fromJS(opProps)

function makeChange(root, newState, message) {
const actor = root._state.get('actorId')
const seq = root._state.getIn(['opSet', 'clock', actor], 0) + 1
const deps = root._state.getIn(['opSet', 'deps']).remove(actor)
const change = fromJS({actor, seq, deps, message})
.set('ops', newState.getIn(['opSet', 'local']))
return FreezeAPI.applyChanges(root, List.of(change), true)
}
///// Automerge.* API

@@ -132,13 +121,4 @@

function checkTarget(funcName, target, needMutable) {
if (!target || !target._state || !target._objectId ||
!target._state.hasIn(['opSet', 'byObject', target._objectId])) {
throw new TypeError('The first argument to Automerge.' + funcName +
' must be the object to modify, but you passed ' + JSON.stringify(target))
}
if (needMutable && (!target._change || !target._change.mutable)) {
throw new TypeError('Automerge.' + funcName + ' requires a writable object as first argument, ' +
'but the one you passed is read-only. Please use Automerge.change() ' +
'to get a writable version.')
}
function initImmutable(actorId) {
return ImmutableAPI.init(actorId || uuid())
}

@@ -191,2 +171,6 @@

function loadImmutable(string, actorId) {
return ImmutableAPI.applyChanges(ImmutableAPI.init(actorId), transit.fromJSON(string), false)
}
function save(doc) {

@@ -229,13 +213,2 @@ checkTarget('save', doc)

function merge(local, remote) {
checkTarget('merge', local)
if (local._state.get('actorId') === remote._state.get('actorId')) {
throw new RangeError('Cannot merge an actor with itself')
}
const clock = local._state.getIn(['opSet', 'clock'])
const changes = OpSet.getMissingChanges(remote._state.get('opSet'), clock)
return FreezeAPI.applyChanges(local, changes, true)
}
// Returns true if all components of clock1 are less than or equal to those of clock2.

@@ -270,2 +243,19 @@ // Returns false if there is at least one component in which clock1 is greater than clock2

function getConflicts(doc, list) {
checkTarget('getConflicts', doc)
const opSet = doc._state.get('opSet')
const objectId = list._objectId
if (!objectId || opSet.getIn(['byObject', objectId, '_init', 'action']) !== 'makeList') {
throw new TypeError('The second argument to Automerge.getConflicts must be a list object')
}
const context = {
cache: {},
instantiateObject (opSet, objectId) {
return opSet.getIn(['cache', objectId])
}
}
return List(OpSet.listIterator(opSet, objectId, 'conflicts', context))
}
function getChanges(oldState, newState) {

@@ -283,12 +273,22 @@ checkTarget('getChanges', oldState)

function applyChanges(doc, changes) {
checkTarget('applyChanges', doc)
return FreezeAPI.applyChanges(doc, fromJS(changes), true)
function getChangesForActor(state, actorId) {
checkTarget('getChanges', state)
// I might want to validate the actorId here
return OpSet.getChangesForActor(state._state.get('opSet'), actorId).toJS()
}
function getMissingDeps(doc) {
checkTarget('getMissingDeps', doc)
return OpSet.getMissingDeps(doc._state.get('opSet'))
}
module.exports = {
init, change, merge, diff, assign, load, save, equals, inspect, getHistory,
getChanges, applyChanges, Text,
initImmutable, loadImmutable, getConflicts,
getChanges, getChangesForActor, applyChanges, getMissingDeps, Text,
DocSet: require('./doc_set'),
WatchableDoc: require('./watchable_doc'),
Connection: require('./connection')
}

@@ -72,17 +72,35 @@ const { Map, List, Set } = require('immutable')

let list = []
Object.defineProperty(list, '_objectId', {value: edit.obj})
Object.defineProperty(list, '_objectId', {value: edit.obj})
Object.defineProperty(list, '_conflicts', {value: Object.freeze([])})
return opSet.setIn(['cache', edit.obj], Object.freeze(list))
}
let value = edit.link ? opSet.getIn(['cache', edit.value]) : edit.value
let list = opSet.getIn(['cache', edit.obj]).slice()
Object.defineProperty(list, '_objectId', {value: edit.obj})
let conflict = null
if (edit.conflicts) {
conflict = {}
for (let c of edit.conflicts) {
conflict[c.actor] = c.link ? opSet.getIn(['cache', c.value]) : c.value
}
Object.freeze(conflict)
}
let list = opSet.getIn(['cache', edit.obj])
const value = edit.link ? opSet.getIn(['cache', edit.value]) : edit.value
const conflicts = list._conflicts.slice()
list = list.slice() // shallow clone
Object.defineProperty(list, '_objectId', {value: edit.obj})
Object.defineProperty(list, '_conflicts', {value: conflicts})
if (edit.action === 'insert') {
list.splice(edit.index, 0, value)
conflicts.splice(edit.index, 0, conflict)
} else if (edit.action === 'set') {
list[edit.index] = value
conflicts[edit.index] = conflict
} else if (edit.action === 'remove') {
list.splice(edit.index, 1)
conflicts.splice(edit.index, 1)
} else throw 'Unknown action type: ' + edit.action
Object.freeze(conflicts)
return opSet.setIn(['cache', edit.obj], Object.freeze(list))

@@ -95,10 +113,31 @@ }

let changed = false
let list = opSet.getIn(['cache', ref.get('obj')])
if (!isObject(list[index]) || list[index]._objectId !== ref.get('value')) return opSet
const value = opSet.getIn(['cache', ref.get('value')])
const conflicts = list._conflicts.slice()
list = list.slice() // shallow clone
Object.defineProperty(list, '_objectId', {value: ref.get('obj')})
list[index] = opSet.getIn(['cache', ref.get('value')])
Object.defineProperty(list, '_objectId', {value: ref.get('obj')})
Object.defineProperty(list, '_conflicts', {value: conflicts})
return opSet.setIn(['cache', ref.get('obj')], Object.freeze(list))
if (isObject(list[index]) && list[index]._objectId === ref.get('value')) {
list[index] = value
changed = true
}
if (isObject(conflicts[index])) {
for (let actor of Object.keys(conflicts[index])) {
const conflict = conflicts[index][actor]
if (isObject(conflict) && conflict._objectId === ref.get('value')) {
conflicts[index] = Object.assign({}, conflicts[index])
conflicts[index][actor] = value
Object.freeze(conflicts[index])
changed = true
}
}
}
if (changed) {
Object.freeze(conflicts)
opSet = opSet.setIn(['cache', ref.get('obj')], Object.freeze(list))
}
return opSet
}

@@ -162,3 +201,5 @@

obj = [...OpSet.listIterator(opSet, objectId, 'values', this)]
Object.defineProperty(obj, '_objectId', {value: objectId})
const conflicts = List(OpSet.listIterator(opSet, objectId, 'conflicts', this)).toJS()
Object.defineProperty(obj, '_objectId', {value: objectId})
Object.defineProperty(obj, '_conflicts', {value: Object.freeze(conflicts)})
} else if (objType === 'makeText') {

@@ -165,0 +206,0 @@ obj = new Text(opSet, objectId)

@@ -72,18 +72,29 @@ const { Map, List, Set } = require('immutable')

function patchList(opSet, objectId, index, action, op) {
function getConflicts(ops) {
const conflicts = []
for (let op of ops.shift()) {
let conflict = {actor: op.get('actor'), value: op.get('value')}
if (op.get('action') === 'link') conflict.link = true
conflicts.push(conflict)
}
return conflicts
}
function patchList(opSet, objectId, index, action, ops) {
const objType = opSet.getIn(['byObject', objectId, '_init', 'action'])
const firstOp = ops ? ops.first() : null
let elemIds = opSet.getIn(['byObject', objectId, '_elemIds'])
let value = op ? op.get('value') : null
let value = firstOp ? firstOp.get('value') : null
let edit = {action, type: (objType === 'makeText') ? 'text' : 'list', obj: objectId, index}
if (op && op.get('action') === 'link') {
if (firstOp && firstOp.get('action') === 'link') {
edit.link = true
value = {obj: op.get('value')}
value = {obj: firstOp.get('value')}
}
if (action === 'insert') {
elemIds = elemIds.insertIndex(index, op.get('key'), value)
edit.value = op.get('value')
elemIds = elemIds.insertIndex(index, firstOp.get('key'), value)
edit.value = firstOp.get('value')
} else if (action === 'set') {
elemIds = elemIds.setValue(op.get('key'), value)
edit.value = op.get('value')
elemIds = elemIds.setValue(firstOp.get('key'), value)
edit.value = firstOp.get('value')
} else if (action === 'remove') {

@@ -93,2 +104,3 @@ elemIds = elemIds.removeIndex(index)

if (ops && ops.size > 1) edit.conflicts = getConflicts(ops)
opSet = opSet.setIn(['byObject', objectId, '_elemIds'], elemIds)

@@ -107,3 +119,3 @@ return [opSet, [edit]]

} else {
return patchList(opSet, objectId, index, 'set', ops.first())
return patchList(opSet, objectId, index, 'set', ops)
}

@@ -124,3 +136,3 @@

return patchList(opSet, objectId, index + 1, 'insert', ops.first())
return patchList(opSet, objectId, index + 1, 'insert', ops)
}

@@ -142,10 +154,3 @@ }

if (ops.size > 1) {
edit.conflicts = []
for (let op of ops.shift()) {
let conflict = {actor: op.get('actor'), value: op.get('value')}
if (op.get('action') === 'link') conflict.link = true
edit.conflicts.push(conflict)
}
}
if (ops.size > 1) edit.conflicts = getConflicts(ops)
}

@@ -286,2 +291,26 @@ return [opSet, [edit]]

function getChangesForActor(opSet, forActor, afterSeq) {
afterSeq = afterSeq || 0
return opSet.get('states')
.filter((states, actor) => actor === forActor)
.map((states, actor) => states.skip(afterSeq))
.valueSeq()
.flatten(1)
.map(state => state.get('change'))
}
function getMissingDeps(opSet) {
let missing = {}
for (let change of opSet.get('queue')) {
const deps = change.get('deps').set(change.get('actor'), change.get('seq') - 1)
deps.forEach((depSeq, depActor) => {
if (opSet.getIn(['clock', depActor], 0) < depSeq) {
missing[depActor] = Math.max(depSeq, missing[depActor] || 0)
}
})
}
return missing
}
function getFieldOps(opSet, objectId, key) {

@@ -421,2 +450,9 @@ return opSet.getIn(['byObject', objectId, key], List())

case 'elems': return {done: false, value: [index, elem]}
case 'conflicts':
let conflict = null
if (ops.size > 1) {
conflict = ops.shift().toMap()
.mapEntries(([_, op]) => [op.get('actor'), getOpValue(opSet, op, context)])
}
return {done: false, value: conflict}
}

@@ -433,5 +469,5 @@ }

module.exports = {
init, addLocalOp, addChange, getMissingChanges,
init, addLocalOp, addChange, getMissingChanges, getChangesForActor, getMissingDeps,
getObjectFields, getObjectField, getObjectConflicts,
listElemByIndex, listLength, listIterator, ROOT_ID
}

@@ -62,3 +62,8 @@ const { List, fromJS } = require('immutable')

}
const deleted = []
for (let n = 0; n < deleteCount; n++) {
deleted.push(OpSet.listElemByIndex(context.state.get('opSet'), listId, start + n, context))
}
context.state = context.splice(context.state, listId, start, deleteCount, values)
return deleted
},

@@ -65,0 +70,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc