Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass a path to the observed object to observable callback #315

Open
wants to merge 1 commit into
base: performance
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions @types/automerge/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ declare module 'automerge' {
observable?: Observable
}

type PatchCallback<T> = (patch: Patch, before: T, after: T, local: boolean, changes: Uint8Array[]) => void
type ObserverCallback<T> = (diff: ObjectDiff, before: T, after: T, local: boolean, changes: Uint8Array[]) => void
type JSONPath = (string | number)[]
type PatchCallback<T> = (patch: Patch, before: T, after: T, local: boolean, changes: Uint8Array[], path: JSONPath) => void
type ObserverCallback<T> = (diff: ObjectDiff, before: T, after: T, local: boolean, changes: Uint8Array[], path: JSONPath) => void

class Observable {
observe<T>(object: T, callback: ObserverCallback<T>): void
Expand Down
11 changes: 7 additions & 4 deletions frontend/observable.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,26 @@ class Observable {
* changes that were applied to the document (as Uint8Arrays).
*/
patchCallback(patch, before, after, local, changes) {
this._objectUpdate(patch.diffs, before, after, local, changes)
this._objectUpdate(patch.diffs, before, after, local, changes, [])
}

/**
* Recursively walks a patch and calls the callbacks for all objects that
* appear in the patch.
*/
_objectUpdate(diff, before, after, local, changes) {
_objectUpdate(diff, before, after, local, changes, path) {
if (!diff.objectId) return
if (this.observers[diff.objectId]) {
for (let callback of this.observers[diff.objectId]) {
callback(diff, before, after, local, changes)
callback(diff, before, after, local, changes, path)
}
}

if (!diff.props) return
for (let propName of Object.keys(diff.props)) {
for (let opId of Object.keys(diff.props[propName])) {
let childDiff = diff.props[propName][opId], childBefore, childAfter
let pathElem = propName

if (diff.type === 'map') {
childBefore = before && before[CONFLICTS] && before[CONFLICTS][propName] &&
Expand All @@ -59,6 +60,7 @@ class Observable {
}
childAfter = after && after[CONFLICTS] && after[CONFLICTS][index] &&
after[CONFLICTS][index][opId]
pathElem = index

} else if (diff.type === 'text') {
const index = parseInt(propName)
Expand All @@ -67,9 +69,10 @@ class Observable {
childBefore = before && before.get(index)
}
childAfter = after && after.get(index)
pathElem = index
}

this._objectUpdate(childDiff, childBefore, childAfter, local, changes)
this._objectUpdate(childDiff, childBefore, childAfter, local, changes, path.concat([pathElem]))
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions test/observable_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('Automerge.Observable', () => {
it('allows registering a callback on the root object', () => {
let observable = new Automerge.Observable(), callbackChanges
let doc = Automerge.init({observable}), actor = Automerge.getActorId(doc)
observable.observe(doc, (diff, before, after, local, changes) => {
observable.observe(doc, (diff, before, after, local, changes, path) => {
callbackChanges = changes
assert.deepStrictEqual(diff, {
objectId: '_root', type: 'map', props: {bird: {[`1@${actor}`]: {value: 'Goldfinch'}}}
Expand All @@ -14,6 +14,7 @@ describe('Automerge.Observable', () => {
assert.deepStrictEqual(after, {bird: 'Goldfinch'})
assert.strictEqual(local, true)
assert.strictEqual(changes.length, 1)
assert.deepStrictEqual(path, [])
})
doc = Automerge.change(doc, doc => doc.bird = 'Goldfinch')
assert.strictEqual(callbackChanges.length, 1)
Expand All @@ -25,7 +26,7 @@ describe('Automerge.Observable', () => {
let observable = new Automerge.Observable(), callbackCalled = false
let doc = Automerge.from({text: new Automerge.Text()}, {observable})
let actor = Automerge.getActorId(doc)
observable.observe(doc.text, (diff, before, after, local) => {
observable.observe(doc.text, (diff, before, after, local, changes, path) => {
callbackCalled = true
assert.deepStrictEqual(diff, {
objectId: `1@${actor}`, type: 'text', edits: [
Expand All @@ -41,6 +42,7 @@ describe('Automerge.Observable', () => {
assert.deepStrictEqual(before.toString(), '')
assert.deepStrictEqual(after.toString(), 'abc')
assert.deepStrictEqual(local, true)
assert.deepStrictEqual(path, ['text'])
})
doc = Automerge.change(doc, doc => doc.text.insertAt(0, 'a', 'b', 'c'))
assert.strictEqual(callbackCalled, true)
Expand Down Expand Up @@ -73,14 +75,15 @@ describe('Automerge.Observable', () => {
let observable = new Automerge.Observable(), callbackCalled = false
let doc = Automerge.from({todos: [{title: 'Buy milk', done: false}]}, {observable})
const actor = Automerge.getActorId(doc)
observable.observe(doc.todos[0], (diff, before, after, local) => {
observable.observe(doc.todos[0], (diff, before, after, local, changes, path) => {
callbackCalled = true
assert.deepStrictEqual(diff, {
objectId: `2@${actor}`, type: 'map', props: {done: {[`5@${actor}`]: {value: true}}}
})
assert.deepStrictEqual(before, {title: 'Buy milk', done: false})
assert.deepStrictEqual(after, {title: 'Buy milk', done: true})
assert.strictEqual(local, true)
assert.deepStrictEqual(path, ['todos', 0])
})
doc = Automerge.change(doc, doc => doc.todos[0].done = true)
assert.strictEqual(callbackCalled, true)
Expand Down Expand Up @@ -113,14 +116,15 @@ describe('Automerge.Observable', () => {
doc.todos = new Automerge.Table()
rowId = doc.todos.add({title: 'Buy milk', done: false})
})
observable.observe(doc.todos.byId(rowId), (diff, before, after, local) => {
observable.observe(doc.todos.byId(rowId), (diff, before, after, local, changes, path) => {
callbackCalled = true
assert.deepStrictEqual(diff, {
objectId: `2@${actor}`, type: 'map', props: {done: {[`5@${actor}`]: {value: true}}}
})
assert.deepStrictEqual(before, {id: rowId, title: 'Buy milk', done: false})
assert.deepStrictEqual(after, {id: rowId, title: 'Buy milk', done: true})
assert.strictEqual(local, true)
assert.deepStrictEqual(path, ['todos', rowId])
})
doc = Automerge.change(doc, doc => doc.todos.byId(rowId).done = true)
assert.strictEqual(callbackCalled, true)
Expand All @@ -133,19 +137,15 @@ describe('Automerge.Observable', () => {
doc.text = new Automerge.Text()
doc.text.insertAt(0, 'a', 'b', {start: 'bold'}, 'c', {end: 'bold'})
})
observable.observe(doc, (patch, before, after) => {
console.log(JSON.stringify(patch, null, 4))
console.log('before =', before.text.elems)
console.log('after =', after.text.elems)
})
observable.observe(doc.text.get(2), (diff, before, after, local) => {
observable.observe(doc.text.get(2), (diff, before, after, local, changes, path) => {
callbackCalled = true
assert.deepStrictEqual(diff, {
objectId: `4@${actor}`, type: 'map', props: {start: {[`9@${actor}`]: {value: 'italic'}}}
})
assert.deepStrictEqual(before, {start: 'bold'})
assert.deepStrictEqual(after, {start: 'italic'})
assert.strictEqual(local, true)
assert.deepStrictEqual(path, ['text', 2])
})
doc = Automerge.change(doc, doc => doc.text.get(2).start = 'italic')
assert.strictEqual(callbackCalled, true)
Expand Down
3 changes: 2 additions & 1 deletion test/typescript_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ describe('TypeScript support', () => {
let observable = new Automerge.Observable(), callbackCalled = false
let doc = Automerge.from({text: new Automerge.Text()}, {observable})
let actor = Automerge.getActorId(doc)
observable.observe(doc.text, (diff, before, after, local, changes) => {
observable.observe(doc.text, (diff, before, after, local, changes, path) => {
callbackCalled = true
assert.deepStrictEqual(diff.edits, [{action: 'insert', index: 0, elemId: `2@${actor}`}])
assert.deepStrictEqual(diff.props, {0: {[`2@${actor}`]: {value: 'a'}}})
Expand All @@ -592,6 +592,7 @@ describe('TypeScript support', () => {
assert.strictEqual(local, true)
assert.strictEqual(changes.length, 1)
assert.ok(changes[0] instanceof Uint8Array)
assert.deepStrictEqual(path, ['text'])
})
doc = Automerge.change(doc, doc => doc.text.insertAt(0, 'a'))
assert.strictEqual(callbackCalled, true)
Expand Down