Compare commits
2 commits
main
...
clone_fork
Author | SHA1 | Date | |
---|---|---|---|
|
4ca9c51a47 | ||
|
0ad422beb5 |
6 changed files with 53 additions and 37 deletions
|
@ -98,6 +98,9 @@ export function getBackend<T>(doc: Doc<T>): Automerge {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _state<T>(doc: Doc<T>, checkroot = true): InternalState<T> {
|
function _state<T>(doc: Doc<T>, checkroot = true): InternalState<T> {
|
||||||
|
if (typeof doc !== 'object') {
|
||||||
|
throw new RangeError("must be the document root")
|
||||||
|
}
|
||||||
const state = Reflect.get(doc, STATE)
|
const state = Reflect.get(doc, STATE)
|
||||||
if (state === undefined || (checkroot && _obj(doc) !== "_root")) {
|
if (state === undefined || (checkroot && _obj(doc) !== "_root")) {
|
||||||
throw new RangeError("must be the document root")
|
throw new RangeError("must be the document root")
|
||||||
|
@ -164,14 +167,23 @@ export function init<T>(_opts?: ActorId | InitOptions<T>): Doc<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a copy of an automerge document.
|
* Make a copy of an automerge document. By default it allocates a new actorId so the copy can be later merged.
|
||||||
*/
|
*/
|
||||||
export function clone<T>(doc: Doc<T>): Doc<T> {
|
export function view<T>(doc: Doc<T>, heads: Heads): Doc<T> {
|
||||||
const state = _state(doc)
|
const state = _state(doc)
|
||||||
const handle = state.heads ? state.handle.forkAt(state.heads) : state.handle.fork()
|
const handle = state.handle
|
||||||
const clonedDoc: any = handle.materialize("/", undefined, {...state, handle})
|
return state.handle.materialize("/", heads, { ...state, handle, heads }) as any
|
||||||
|
}
|
||||||
|
|
||||||
return clonedDoc
|
/**
|
||||||
|
* Make a copy of an automerge document. By default it allocates a new actorId so the copy can be later merged.
|
||||||
|
*/
|
||||||
|
export function clone<T>(doc: Doc<T>, _opts?: ActorId | InitOptions<T>): Doc<T> {
|
||||||
|
const state = _state(doc)
|
||||||
|
const heads = state.heads
|
||||||
|
const opts = importOpts(_opts)
|
||||||
|
const handle = state.handle.fork(opts.actor, heads)
|
||||||
|
return handle.applyPatches(doc, { ... state, heads, handle })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Explicity free the memory backing a document. Note that this is note
|
/** Explicity free the memory backing a document. Note that this is note
|
||||||
|
@ -264,10 +276,8 @@ export function change<T>(doc: Doc<T>, options: string | ChangeOptions<T> | Chan
|
||||||
function progressDocument<T>(doc: Doc<T>, heads: Heads, callback?: PatchCallback<T>): Doc<T> {
|
function progressDocument<T>(doc: Doc<T>, heads: Heads, callback?: PatchCallback<T>): Doc<T> {
|
||||||
let state = _state(doc)
|
let state = _state(doc)
|
||||||
let nextState = {...state, heads: undefined};
|
let nextState = {...state, heads: undefined};
|
||||||
// @ts-ignore
|
|
||||||
let nextDoc = state.handle.applyPatches(doc, nextState, callback)
|
let nextDoc = state.handle.applyPatches(doc, nextState, callback)
|
||||||
state.heads = heads
|
state.heads = heads
|
||||||
if (nextState.freeze) {Object.freeze(nextDoc)}
|
|
||||||
return nextDoc
|
return nextDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +294,7 @@ function _change<T>(doc: Doc<T>, options: ChangeOptions<T>, callback: ChangeFn<T
|
||||||
throw new RangeError("must be the document root");
|
throw new RangeError("must be the document root");
|
||||||
}
|
}
|
||||||
if (state.heads) {
|
if (state.heads) {
|
||||||
throw new RangeError("Attempting to use an outdated Automerge document")
|
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.")
|
||||||
}
|
}
|
||||||
if (_readonly(doc) === false) {
|
if (_readonly(doc) === false) {
|
||||||
throw new RangeError("Calls to Automerge.change cannot be nested")
|
throw new RangeError("Calls to Automerge.change cannot be nested")
|
||||||
|
@ -331,7 +341,7 @@ export function emptyChange<T>(doc: Doc<T>, options: string | ChangeOptions<T>)
|
||||||
const state = _state(doc)
|
const state = _state(doc)
|
||||||
|
|
||||||
if (state.heads) {
|
if (state.heads) {
|
||||||
throw new RangeError("Attempting to use an outdated Automerge document")
|
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.")
|
||||||
}
|
}
|
||||||
if (_readonly(doc) === false) {
|
if (_readonly(doc) === false) {
|
||||||
throw new RangeError("Calls to Automerge.change cannot be nested")
|
throw new RangeError("Calls to Automerge.change cannot be nested")
|
||||||
|
@ -616,7 +626,7 @@ export function applyChanges<T>(doc: Doc<T>, changes: Change[], opts?: ApplyOpti
|
||||||
const state = _state(doc)
|
const state = _state(doc)
|
||||||
if (!opts) {opts = {}}
|
if (!opts) {opts = {}}
|
||||||
if (state.heads) {
|
if (state.heads) {
|
||||||
throw new RangeError("Attempting to use an outdated Automerge document")
|
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.")
|
||||||
}
|
}
|
||||||
if (_readonly(doc) === false) {
|
if (_readonly(doc) === false) {
|
||||||
throw new RangeError("Calls to Automerge.change cannot be nested")
|
throw new RangeError("Calls to Automerge.change cannot be nested")
|
||||||
|
@ -721,7 +731,7 @@ export function receiveSyncMessage<T>(doc: Doc<T>, inState: SyncState, message:
|
||||||
if (!opts) {opts = {}}
|
if (!opts) {opts = {}}
|
||||||
const state = _state(doc)
|
const state = _state(doc)
|
||||||
if (state.heads) {
|
if (state.heads) {
|
||||||
throw new RangeError("Attempting to change an out of date document - set at: " + _trace(doc));
|
throw new RangeError("Attempting to change an outdated document. Use Automerge.clone() if you wish to make a writable copy.")
|
||||||
}
|
}
|
||||||
if (_readonly(doc) === false) {
|
if (_readonly(doc) === false) {
|
||||||
throw new RangeError("Calls to Automerge.change cannot be nested")
|
throw new RangeError("Calls to Automerge.change cannot be nested")
|
||||||
|
|
|
@ -7,6 +7,22 @@ describe('Automerge', () => {
|
||||||
it('should init clone and free', () => {
|
it('should init clone and free', () => {
|
||||||
let doc1 = Automerge.init()
|
let doc1 = Automerge.init()
|
||||||
let doc2 = Automerge.clone(doc1);
|
let doc2 = Automerge.clone(doc1);
|
||||||
|
|
||||||
|
// this is only needed if weakrefs are not supported
|
||||||
|
Automerge.free(doc1)
|
||||||
|
Automerge.free(doc2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be able to make a view with specifc heads', () => {
|
||||||
|
let doc1 = Automerge.init()
|
||||||
|
let doc2 = Automerge.change(doc1, (d) => d.value = 1)
|
||||||
|
let heads2 = Automerge.getHeads(doc2)
|
||||||
|
let doc3 = Automerge.change(doc2, (d) => d.value = 2)
|
||||||
|
let doc2_v2 = Automerge.view(doc3, heads2)
|
||||||
|
assert.deepEqual(doc2, doc2_v2)
|
||||||
|
let doc2_v2_clone = Automerge.clone(doc2, "aabbcc")
|
||||||
|
assert.deepEqual(doc2, doc2_v2_clone)
|
||||||
|
assert.equal(Automerge.getActorId(doc2_v2_clone), "aabbcc")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handle basic set and read on root object', () => {
|
it('handle basic set and read on root object', () => {
|
||||||
|
|
|
@ -231,14 +231,14 @@ describe('Automerge', () => {
|
||||||
s2 = Automerge.change(s1, doc2 => doc2.two = 2)
|
s2 = Automerge.change(s1, doc2 => doc2.two = 2)
|
||||||
doc1.one = 1
|
doc1.one = 1
|
||||||
})
|
})
|
||||||
}, /Attempting to use an outdated Automerge document/)
|
}, /Attempting to change an outdated document/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow the same base document to be used for multiple changes', () => {
|
it('should not allow the same base document to be used for multiple changes', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
Automerge.change(s1, doc => doc.one = 1)
|
Automerge.change(s1, doc => doc.one = 1)
|
||||||
Automerge.change(s1, doc => doc.two = 2)
|
Automerge.change(s1, doc => doc.two = 2)
|
||||||
}, /Attempting to use an outdated Automerge document/)
|
}, /Attempting to change an outdated document/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow a document to be cloned', () => {
|
it('should allow a document to be cloned', () => {
|
||||||
|
|
9
rust/automerge-wasm/index.d.ts
vendored
9
rust/automerge-wasm/index.d.ts
vendored
|
@ -199,12 +199,11 @@ export class Automerge {
|
||||||
getMissingDeps(heads?: Heads): Heads;
|
getMissingDeps(heads?: Heads): Heads;
|
||||||
|
|
||||||
// memory management
|
// memory management
|
||||||
free(): void;
|
free(): void; // only needed if weak-refs are unsupported
|
||||||
clone(actor?: string): Automerge;
|
clone(actor?: string): Automerge; // TODO - remove, this is dangerous
|
||||||
fork(actor?: string): Automerge;
|
fork(actor?: string, heads?: Heads): Automerge;
|
||||||
forkAt(heads: Heads, actor?: string): Automerge;
|
|
||||||
|
|
||||||
// dump internal state to console.log
|
// dump internal state to console.log - for debugging
|
||||||
dump(): void;
|
dump(): void;
|
||||||
|
|
||||||
// experimental api can go here
|
// experimental api can go here
|
||||||
|
|
|
@ -98,24 +98,15 @@ impl Automerge {
|
||||||
Ok(automerge)
|
Ok(automerge)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fork(&mut self, actor: Option<String>) -> Result<Automerge, JsValue> {
|
pub fn fork(&mut self, actor: Option<String>, heads: JsValue) -> Result<Automerge, JsValue> {
|
||||||
let mut automerge = Automerge {
|
let heads: Result<Vec<am::ChangeHash>, _> = JS(heads).try_into();
|
||||||
doc: self.doc.fork(),
|
let doc = if let Ok(heads) = heads {
|
||||||
freeze: self.freeze,
|
self.doc.fork_at(&heads)?
|
||||||
external_types: self.external_types.clone(),
|
} else {
|
||||||
|
self.doc.fork()
|
||||||
};
|
};
|
||||||
if let Some(s) = actor {
|
|
||||||
let actor = automerge::ActorId::from(hex::decode(s).map_err(to_js_err)?.to_vec());
|
|
||||||
automerge.doc.set_actor(actor);
|
|
||||||
}
|
|
||||||
Ok(automerge)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = forkAt)]
|
|
||||||
pub fn fork_at(&mut self, heads: JsValue, actor: Option<String>) -> Result<Automerge, JsValue> {
|
|
||||||
let deps: Vec<_> = JS(heads).try_into()?;
|
|
||||||
let mut automerge = Automerge {
|
let mut automerge = Automerge {
|
||||||
doc: self.doc.fork_at(&deps)?,
|
doc,
|
||||||
freeze: self.freeze,
|
freeze: self.freeze,
|
||||||
external_types: self.external_types.clone(),
|
external_types: self.external_types.clone(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -425,7 +425,7 @@ describe('Automerge', () => {
|
||||||
assert.deepEqual(doc2.getWithType(c, "d"), ["str", "dd"])
|
assert.deepEqual(doc2.getWithType(c, "d"), ["str", "dd"])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow you to forkAt a heads', () => {
|
it('should allow you to fork at a heads', () => {
|
||||||
const A = create("aaaaaa")
|
const A = create("aaaaaa")
|
||||||
A.put("/", "key1", "val1");
|
A.put("/", "key1", "val1");
|
||||||
A.put("/", "key2", "val2");
|
A.put("/", "key2", "val2");
|
||||||
|
@ -436,8 +436,8 @@ describe('Automerge', () => {
|
||||||
A.merge(B)
|
A.merge(B)
|
||||||
const heads2 = A.getHeads();
|
const heads2 = A.getHeads();
|
||||||
A.put("/", "key5", "val5");
|
A.put("/", "key5", "val5");
|
||||||
assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", heads1))
|
assert.deepEqual(A.fork(undefined, heads1).materialize("/"), A.materialize("/", heads1))
|
||||||
assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", heads2))
|
assert.deepEqual(A.fork(undefined, heads2).materialize("/"), A.materialize("/", heads2))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle merging text conflicts then saving & loading', () => {
|
it('should handle merging text conflicts then saving & loading', () => {
|
||||||
|
|
Loading…
Reference in a new issue