1+ import { FanOutUnsubscribe } from 'thingies/es2020/fanout' ;
12import { ITimestampStruct , Patch , compare } from '../../json-crdt-patch' ;
23import { printTree } from '../../util/print/printTree' ;
34import { AvlMap } from '../../util/trees/avl/AvlMap' ;
@@ -6,32 +7,84 @@ import {first, next} from '../../util/trees/util';
67import type { Printable } from '../../util/print/types' ;
78
89export class PatchLog implements Printable {
10+ /**
11+ * Creates a `PatchLog` instance from a newly JSON CRDT model. Checks if
12+ * the model API buffer has any initial operations applied, if yes, it
13+ * uses them to create the initial state of the log.
14+ *
15+ * @param model A new JSON CRDT model, just created with
16+ * `Model.withLogicalClock()` or `Model.withServerClock()`.
17+ * @returns A new `PatchLog` instance.
18+ */
919 public static fromNewModel ( model : Model < any > ) : PatchLog {
10- const start = new Model ( model . clock . clone ( ) ) ;
11- const log = new PatchLog ( start ) ;
20+ const clock = model . clock . clone ( ) ;
21+ const log = new PatchLog ( ( ) => new Model ( clock ) ) ;
1222 const api = model . api ;
13- if ( api . builder . patch . ops . length ) log . push ( api . flush ( ) ) ;
23+ if ( api . builder . patch . ops . length ) log . end . applyPatch ( api . flush ( ) ) ;
1424 return log ;
1525 }
1626
27+ /**
28+ * Model factory function that creates a new JSON CRDT model instance, which
29+ * is used as the starting point of the log. It is called every time a new
30+ * model is needed to replay the log.
31+ */
32+ public readonly start : ( ) => Model ;
33+
34+ /**
35+ * The end of the log, the current state of the document. It is the model
36+ * instance that is used to apply new patches to the log.
37+ */
38+ public readonly end : Model ;
39+
40+ /**
41+ * The patches in the log, stored in an AVL tree for efficient replaying. The
42+ * collection of patches which are applied to the `start()` model to reach
43+ * the `end` model.
44+ */
1745 public readonly patches = new AvlMap < ITimestampStruct , Patch > ( compare ) ;
46+ private _patchesUnsub : FanOutUnsubscribe ;
1847
19- constructor ( public readonly start : Model ) { }
48+ constructor ( start : ( ) => Model ) {
49+ this . start = start ;
50+ this . end = start ( ) ;
51+ this . _patchesUnsub = this . end . api . onPatch . listen ( ( patch ) => {
52+ const id = patch . getId ( ) ;
53+ if ( ! id ) return ;
54+ this . patches . set ( id , patch ) ;
55+ } ) ;
56+ }
2057
21- public push ( patch : Patch ) : void {
22- const id = patch . getId ( ) ;
23- if ( ! id ) return ;
24- this . patches . set ( id , patch ) ;
58+ /**
59+ * Call this method to destroy the `PatchLog` instance. It unsubscribes from
60+ * the model's `onPatch` event listener.
61+ */
62+ public destroy ( ) {
63+ this . _patchesUnsub ( ) ;
2564 }
2665
66+ /**
67+ * Creates a new model instance using the `start()` factory function and
68+ * replays all patches in the log to reach the current state of the document.
69+ *
70+ * @returns A new model instance with all patches replayed.
71+ */
2772 public replayToEnd ( ) : Model {
28- const clone = this . start . clone ( ) ;
73+ const clone = this . start ( ) . clone ( ) ;
2974 for ( let node = first ( this . patches . root ) ; node ; node = next ( node ) ) clone . applyPatch ( node . v ) ;
3075 return clone ;
3176 }
3277
78+ /**
79+ * Replays the patch log until a specified timestamp, including the patch
80+ * at the given timestamp. The model returned is a new instance of `start()`
81+ * with patches replayed up to the given timestamp.
82+ *
83+ * @param ts Timestamp ID of the patch to replay to.
84+ * @returns A new model instance with patches replayed up to the given timestamp.
85+ */
3386 public replayTo ( ts : ITimestampStruct ) : Model {
34- const clone = this . start . clone ( ) ;
87+ const clone = this . start ( ) . clone ( ) ;
3588 for ( let node = first ( this . patches . root ) ; node && compare ( ts , node . k ) >= 0 ; node = next ( node ) )
3689 clone . applyPatch ( node . v ) ;
3790 return clone ;
@@ -45,14 +98,16 @@ export class PatchLog implements Printable {
4598 return (
4699 `log` +
47100 printTree ( tab , [
48- ( tab ) => this . start . toString ( tab ) ,
101+ ( tab ) => `start` + printTree ( tab , [ tab => this . start ( ) . toString ( tab ) ] ) ,
49102 ( ) => '' ,
50103 ( tab ) =>
51- 'history' +
104+ 'history' +
52105 printTree (
53106 tab ,
54107 log . map ( ( patch , i ) => ( tab ) => `${ i } : ${ patch . toString ( tab ) } ` ) ,
55108 ) ,
109+ ( ) => '' ,
110+ ( tab ) => `end` + printTree ( tab , [ tab => this . end . toString ( tab ) ] ) ,
56111 ] )
57112 ) ;
58113 }
0 commit comments