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> {
|
||||
if (typeof doc !== 'object') {
|
||||
throw new RangeError("must be the document root")
|
||||
}
|
||||
const state = Reflect.get(doc, STATE)
|
||||
if (state === undefined || (checkroot && _obj(doc) !== "_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 handle = state.heads ? state.handle.forkAt(state.heads) : state.handle.fork()
|
||||
const clonedDoc: any = handle.materialize("/", undefined, {...state, handle})
|
||||
const handle = 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
|
||||
|
@ -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> {
|
||||
let state = _state(doc)
|
||||
let nextState = {...state, heads: undefined};
|
||||
// @ts-ignore
|
||||
let nextDoc = state.handle.applyPatches(doc, nextState, callback)
|
||||
state.heads = heads
|
||||
if (nextState.freeze) {Object.freeze(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");
|
||||
}
|
||||
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) {
|
||||
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)
|
||||
|
||||
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) {
|
||||
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)
|
||||
if (!opts) {opts = {}}
|
||||
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) {
|
||||
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 = {}}
|
||||
const state = _state(doc)
|
||||
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) {
|
||||
throw new RangeError("Calls to Automerge.change cannot be nested")
|
||||
|
|
|
@ -7,6 +7,22 @@ describe('Automerge', () => {
|
|||
it('should init clone and free', () => {
|
||||
let doc1 = Automerge.init()
|
||||
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', () => {
|
||||
|
|
|
@ -231,14 +231,14 @@ describe('Automerge', () => {
|
|||
s2 = Automerge.change(s1, doc2 => doc2.two = 2)
|
||||
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', () => {
|
||||
assert.throws(() => {
|
||||
Automerge.change(s1, doc => doc.one = 1)
|
||||
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', () => {
|
||||
|
|
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;
|
||||
|
||||
// memory management
|
||||
free(): void;
|
||||
clone(actor?: string): Automerge;
|
||||
fork(actor?: string): Automerge;
|
||||
forkAt(heads: Heads, actor?: string): Automerge;
|
||||
free(): void; // only needed if weak-refs are unsupported
|
||||
clone(actor?: string): Automerge; // TODO - remove, this is dangerous
|
||||
fork(actor?: string, heads?: Heads): Automerge;
|
||||
|
||||
// dump internal state to console.log
|
||||
// dump internal state to console.log - for debugging
|
||||
dump(): void;
|
||||
|
||||
// experimental api can go here
|
||||
|
|
|
@ -98,24 +98,15 @@ impl Automerge {
|
|||
Ok(automerge)
|
||||
}
|
||||
|
||||
pub fn fork(&mut self, actor: Option<String>) -> Result<Automerge, JsValue> {
|
||||
let mut automerge = Automerge {
|
||||
doc: self.doc.fork(),
|
||||
freeze: self.freeze,
|
||||
external_types: self.external_types.clone(),
|
||||
pub fn fork(&mut self, actor: Option<String>, heads: JsValue) -> Result<Automerge, JsValue> {
|
||||
let heads: Result<Vec<am::ChangeHash>, _> = JS(heads).try_into();
|
||||
let doc = if let Ok(heads) = heads {
|
||||
self.doc.fork_at(&heads)?
|
||||
} 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 {
|
||||
doc: self.doc.fork_at(&deps)?,
|
||||
doc,
|
||||
freeze: self.freeze,
|
||||
external_types: self.external_types.clone(),
|
||||
};
|
||||
|
|
|
@ -425,7 +425,7 @@ describe('Automerge', () => {
|
|||
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")
|
||||
A.put("/", "key1", "val1");
|
||||
A.put("/", "key2", "val2");
|
||||
|
@ -436,8 +436,8 @@ describe('Automerge', () => {
|
|||
A.merge(B)
|
||||
const heads2 = A.getHeads();
|
||||
A.put("/", "key5", "val5");
|
||||
assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", heads1))
|
||||
assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", heads2))
|
||||
assert.deepEqual(A.fork(undefined, heads1).materialize("/"), A.materialize("/", heads1))
|
||||
assert.deepEqual(A.fork(undefined, heads2).materialize("/"), A.materialize("/", heads2))
|
||||
})
|
||||
|
||||
it('should handle merging text conflicts then saving & loading', () => {
|
||||
|
|
Loading…
Reference in a new issue