@dabble/patches 0.7.9 → 0.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseDoc-_Rsau70J.d.ts → BaseDoc-BfVJNeCi.d.ts} +29 -16
- package/dist/client/BaseDoc.d.ts +1 -1
- package/dist/client/BaseDoc.js +31 -11
- package/dist/client/ClientAlgorithm.d.ts +1 -1
- package/dist/client/LWWAlgorithm.d.ts +1 -1
- package/dist/client/LWWDoc.d.ts +1 -1
- package/dist/client/LWWDoc.js +3 -0
- package/dist/client/OTAlgorithm.d.ts +1 -1
- package/dist/client/OTDoc.d.ts +1 -1
- package/dist/client/OTDoc.js +3 -0
- package/dist/client/Patches.d.ts +1 -1
- package/dist/client/PatchesDoc.d.ts +1 -1
- package/dist/client/factories.d.ts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/net/PatchesSync.d.ts +29 -3
- package/dist/net/PatchesSync.js +138 -16
- package/dist/net/index.d.ts +1 -1
- package/dist/shared/doc-manager.d.ts +1 -1
- package/dist/shared/utils.d.ts +11 -1
- package/dist/shared/utils.js +5 -1
- package/dist/solid/context.d.ts +1 -1
- package/dist/solid/doc-manager.d.ts +1 -1
- package/dist/solid/index.d.ts +1 -1
- package/dist/solid/primitives.d.ts +1 -1
- package/dist/solid/primitives.js +35 -12
- package/dist/solid/utils.d.ts +4 -0
- package/dist/types.d.ts +24 -7
- package/dist/vue/composables.d.ts +1 -1
- package/dist/vue/composables.js +37 -14
- package/dist/vue/doc-manager.d.ts +1 -1
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/managed-docs.d.ts +1 -1
- package/dist/vue/provider.d.ts +1 -1
- package/dist/vue/utils.d.ts +4 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Signal, Unsubscriber } from './event-signal.js';
|
|
2
2
|
import { JSONPatchOp } from './json-patch/types.js';
|
|
3
|
-
import { Change, PatchesSnapshot,
|
|
3
|
+
import { Change, PatchesSnapshot, SyncedDoc, DocSyncStatus, ChangeMutator } from './types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* OT (Operational Transformation) document implementation.
|
|
@@ -96,9 +96,9 @@ interface PatchesDocOptions {
|
|
|
96
96
|
* This interface is implemented by both OTDoc (Operational Transformation)
|
|
97
97
|
* and LWWDoc (Last-Write-Wins) implementations via BaseDoc.
|
|
98
98
|
*
|
|
99
|
-
* Internal methods (
|
|
99
|
+
* Internal methods (updateSyncStatus, applyChanges, import) are on BaseDoc, not this interface.
|
|
100
100
|
*/
|
|
101
|
-
interface PatchesDoc<T extends object = object> {
|
|
101
|
+
interface PatchesDoc<T extends object = object> extends SyncedDoc {
|
|
102
102
|
/** The unique identifier for this document. */
|
|
103
103
|
readonly id: string;
|
|
104
104
|
/** Current local state (committed + pending merged). */
|
|
@@ -107,8 +107,12 @@ interface PatchesDoc<T extends object = object> {
|
|
|
107
107
|
readonly committedRev: number;
|
|
108
108
|
/** Are there local changes that haven't been committed yet? */
|
|
109
109
|
readonly hasPending: boolean;
|
|
110
|
-
/**
|
|
111
|
-
readonly
|
|
110
|
+
/** Current sync status of this document. */
|
|
111
|
+
readonly syncStatus: DocSyncStatus;
|
|
112
|
+
/** Error from the last failed sync attempt, if any. */
|
|
113
|
+
readonly syncError: Error | null;
|
|
114
|
+
/** Whether the document has completed its initial load. Sticky: once true, never reverts to false. */
|
|
115
|
+
readonly isLoaded: boolean;
|
|
112
116
|
/**
|
|
113
117
|
* Subscribe to be notified when the user makes local changes.
|
|
114
118
|
* Emits the JSON Patch ops captured from the change() call.
|
|
@@ -117,8 +121,8 @@ interface PatchesDoc<T extends object = object> {
|
|
|
117
121
|
readonly onChange: Signal<(ops: JSONPatchOp[]) => void>;
|
|
118
122
|
/** Subscribe to be notified whenever state changes from any source. */
|
|
119
123
|
readonly onUpdate: Signal<(newState: T) => void>;
|
|
120
|
-
/** Subscribe to be notified when
|
|
121
|
-
readonly
|
|
124
|
+
/** Subscribe to be notified when sync status changes. */
|
|
125
|
+
readonly onSyncStatus: Signal<(newStatus: DocSyncStatus) => void>;
|
|
122
126
|
/** Subscribe to be notified whenever the state changes (calls immediately with current state). */
|
|
123
127
|
subscribe(onUpdate: (newValue: T) => void): Unsubscriber;
|
|
124
128
|
/**
|
|
@@ -137,13 +141,15 @@ interface PatchesDoc<T extends object = object> {
|
|
|
137
141
|
* apply locally. The algorithm handles packaging ops, persisting them, and updating
|
|
138
142
|
* the doc's state via `applyChanges()`.
|
|
139
143
|
*
|
|
140
|
-
* Internal methods (
|
|
144
|
+
* Internal methods (updateSyncStatus, applyChanges, import) are on this class but not
|
|
141
145
|
* on the PatchesDoc interface, as they're only used by Algorithm and PatchesSync.
|
|
142
146
|
*/
|
|
143
147
|
declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<T> {
|
|
144
148
|
protected _id: string;
|
|
145
149
|
protected _state: T;
|
|
146
|
-
protected
|
|
150
|
+
protected _syncStatus: DocSyncStatus;
|
|
151
|
+
protected _syncError: Error | null;
|
|
152
|
+
protected _isLoaded: boolean;
|
|
147
153
|
/**
|
|
148
154
|
* Subscribe to be notified when the user makes local changes.
|
|
149
155
|
* Emits the JSON Patch ops captured from the change() call.
|
|
@@ -152,8 +158,8 @@ declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<
|
|
|
152
158
|
readonly onChange: Signal<(ops: JSONPatchOp[]) => void>;
|
|
153
159
|
/** Subscribe to be notified whenever state changes from any source. */
|
|
154
160
|
readonly onUpdate: Signal<(newState: T) => void>;
|
|
155
|
-
/** Subscribe to be notified when
|
|
156
|
-
readonly
|
|
161
|
+
/** Subscribe to be notified when sync status changes. */
|
|
162
|
+
readonly onSyncStatus: Signal<(newStatus: DocSyncStatus) => void>;
|
|
157
163
|
/**
|
|
158
164
|
* Creates an instance of BaseDoc.
|
|
159
165
|
* @param id The unique identifier for this document.
|
|
@@ -164,8 +170,12 @@ declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<
|
|
|
164
170
|
get id(): string;
|
|
165
171
|
/** Current local state (committed + pending merged). */
|
|
166
172
|
get state(): T;
|
|
167
|
-
/**
|
|
168
|
-
get
|
|
173
|
+
/** Current sync status of this document. */
|
|
174
|
+
get syncStatus(): DocSyncStatus;
|
|
175
|
+
/** Error from the last failed sync attempt, if any. */
|
|
176
|
+
get syncError(): Error | null;
|
|
177
|
+
/** Whether the document has completed its initial load. Sticky: once true, never reverts to false. */
|
|
178
|
+
get isLoaded(): boolean;
|
|
169
179
|
/** Last committed revision number from the server. */
|
|
170
180
|
abstract get committedRev(): number;
|
|
171
181
|
/** Are there local changes that haven't been committed yet? */
|
|
@@ -179,11 +189,14 @@ declare abstract class BaseDoc<T extends object = object> implements PatchesDoc<
|
|
|
179
189
|
*/
|
|
180
190
|
change(mutator: ChangeMutator<T>): void;
|
|
181
191
|
/**
|
|
182
|
-
* Updates the
|
|
192
|
+
* Updates the sync status of the document.
|
|
183
193
|
* Called by PatchesSync - not part of the app-facing PatchesDoc interface.
|
|
184
|
-
* @param
|
|
194
|
+
* @param status The new sync status.
|
|
195
|
+
* @param error Optional error when status is 'error'.
|
|
185
196
|
*/
|
|
186
|
-
|
|
197
|
+
updateSyncStatus(status: DocSyncStatus, error?: Error): void;
|
|
198
|
+
/** Latches _isLoaded to true when the doc has data or sync has resolved. */
|
|
199
|
+
protected _checkLoaded(): void;
|
|
187
200
|
/**
|
|
188
201
|
* Applies changes to the document state.
|
|
189
202
|
* Called by Algorithm for local changes and broadcasts - not part of PatchesDoc interface.
|
package/dist/client/BaseDoc.d.ts
CHANGED
package/dist/client/BaseDoc.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
2
|
import { signal } from "../event-signal.js";
|
|
3
3
|
import { createJSONPatch } from "../json-patch/createJSONPatch.js";
|
|
4
|
+
import { isDocLoaded } from "../shared/utils.js";
|
|
4
5
|
class BaseDoc {
|
|
5
6
|
_id;
|
|
6
7
|
_state;
|
|
7
|
-
|
|
8
|
+
_syncStatus = "unsynced";
|
|
9
|
+
_syncError = null;
|
|
10
|
+
_isLoaded = false;
|
|
8
11
|
/**
|
|
9
12
|
* Subscribe to be notified when the user makes local changes.
|
|
10
13
|
* Emits the JSON Patch ops captured from the change() call.
|
|
@@ -13,8 +16,8 @@ class BaseDoc {
|
|
|
13
16
|
onChange = signal();
|
|
14
17
|
/** Subscribe to be notified whenever state changes from any source. */
|
|
15
18
|
onUpdate = signal();
|
|
16
|
-
/** Subscribe to be notified when
|
|
17
|
-
|
|
19
|
+
/** Subscribe to be notified when sync status changes. */
|
|
20
|
+
onSyncStatus = signal();
|
|
18
21
|
/**
|
|
19
22
|
* Creates an instance of BaseDoc.
|
|
20
23
|
* @param id The unique identifier for this document.
|
|
@@ -32,9 +35,17 @@ class BaseDoc {
|
|
|
32
35
|
get state() {
|
|
33
36
|
return this._state;
|
|
34
37
|
}
|
|
35
|
-
/**
|
|
36
|
-
get
|
|
37
|
-
return this.
|
|
38
|
+
/** Current sync status of this document. */
|
|
39
|
+
get syncStatus() {
|
|
40
|
+
return this._syncStatus;
|
|
41
|
+
}
|
|
42
|
+
/** Error from the last failed sync attempt, if any. */
|
|
43
|
+
get syncError() {
|
|
44
|
+
return this._syncError;
|
|
45
|
+
}
|
|
46
|
+
/** Whether the document has completed its initial load. Sticky: once true, never reverts to false. */
|
|
47
|
+
get isLoaded() {
|
|
48
|
+
return this._isLoaded;
|
|
38
49
|
}
|
|
39
50
|
/** Subscribe to be notified whenever the state changes (calls immediately with current state). */
|
|
40
51
|
subscribe(onUpdate) {
|
|
@@ -56,13 +67,22 @@ class BaseDoc {
|
|
|
56
67
|
}
|
|
57
68
|
// --- Internal methods (not on PatchesDoc interface) ---
|
|
58
69
|
/**
|
|
59
|
-
* Updates the
|
|
70
|
+
* Updates the sync status of the document.
|
|
60
71
|
* Called by PatchesSync - not part of the app-facing PatchesDoc interface.
|
|
61
|
-
* @param
|
|
72
|
+
* @param status The new sync status.
|
|
73
|
+
* @param error Optional error when status is 'error'.
|
|
62
74
|
*/
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
75
|
+
updateSyncStatus(status, error) {
|
|
76
|
+
this._syncStatus = status;
|
|
77
|
+
this._syncError = status === "error" ? error ?? null : null;
|
|
78
|
+
this._checkLoaded();
|
|
79
|
+
this.onSyncStatus.emit(status);
|
|
80
|
+
}
|
|
81
|
+
/** Latches _isLoaded to true when the doc has data or sync has resolved. */
|
|
82
|
+
_checkLoaded() {
|
|
83
|
+
if (!this._isLoaded && isDocLoaded(this)) {
|
|
84
|
+
this._isLoaded = true;
|
|
85
|
+
}
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
88
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JSONPatchOp } from '../json-patch/types.js';
|
|
2
2
|
import { PatchesSnapshot, Change } from '../types.js';
|
|
3
|
-
import { a as PatchesDoc } from '../BaseDoc-
|
|
3
|
+
import { a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
4
4
|
import { PatchesStore, TrackedDoc } from './PatchesStore.js';
|
|
5
5
|
import '../json-patch/JSONPatch.js';
|
|
6
6
|
import '@dabble/delta';
|
|
@@ -2,7 +2,7 @@ import { JSONPatchOp } from '../json-patch/types.js';
|
|
|
2
2
|
import { PatchesSnapshot, Change } from '../types.js';
|
|
3
3
|
import { ClientAlgorithm } from './ClientAlgorithm.js';
|
|
4
4
|
import { LWWClientStore } from './LWWClientStore.js';
|
|
5
|
-
import { a as PatchesDoc } from '../BaseDoc-
|
|
5
|
+
import { a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
6
6
|
import { TrackedDoc } from './PatchesStore.js';
|
|
7
7
|
import '../json-patch/JSONPatch.js';
|
|
8
8
|
import '@dabble/delta';
|
package/dist/client/LWWDoc.d.ts
CHANGED
package/dist/client/LWWDoc.js
CHANGED
|
@@ -21,6 +21,7 @@ class LWWDoc extends BaseDoc {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
this._checkLoaded();
|
|
24
25
|
}
|
|
25
26
|
/** Last committed revision number from the server. */
|
|
26
27
|
get committedRev() {
|
|
@@ -45,6 +46,7 @@ class LWWDoc extends BaseDoc {
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
this._checkLoaded();
|
|
48
50
|
this.onUpdate.emit(this._state);
|
|
49
51
|
}
|
|
50
52
|
/**
|
|
@@ -78,6 +80,7 @@ class LWWDoc extends BaseDoc {
|
|
|
78
80
|
}
|
|
79
81
|
this._committedRev = lastCommittedRev;
|
|
80
82
|
this._hasPending = hasPending ?? hasPendingChanges;
|
|
83
|
+
this._checkLoaded();
|
|
81
84
|
this.onUpdate.emit(this._state);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
@@ -2,7 +2,7 @@ import { JSONPatchOp } from '../json-patch/types.js';
|
|
|
2
2
|
import { PatchesSnapshot, Change } from '../types.js';
|
|
3
3
|
import { ClientAlgorithm } from './ClientAlgorithm.js';
|
|
4
4
|
import { OTClientStore } from './OTClientStore.js';
|
|
5
|
-
import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-
|
|
5
|
+
import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
6
6
|
import { TrackedDoc } from './PatchesStore.js';
|
|
7
7
|
import '../json-patch/JSONPatch.js';
|
|
8
8
|
import '@dabble/delta';
|
package/dist/client/OTDoc.d.ts
CHANGED
package/dist/client/OTDoc.js
CHANGED
|
@@ -23,6 +23,7 @@ class OTDoc extends BaseDoc {
|
|
|
23
23
|
if (this._pendingChanges.length > 0) {
|
|
24
24
|
this._state = applyChangesToState(this._committedState, this._pendingChanges);
|
|
25
25
|
}
|
|
26
|
+
this._checkLoaded();
|
|
26
27
|
}
|
|
27
28
|
/** Last committed revision number from the server. */
|
|
28
29
|
get committedRev() {
|
|
@@ -48,6 +49,7 @@ class OTDoc extends BaseDoc {
|
|
|
48
49
|
this._committedRev = snapshot.rev;
|
|
49
50
|
this._pendingChanges = snapshot.changes;
|
|
50
51
|
this._state = createStateFromSnapshot(snapshot);
|
|
52
|
+
this._checkLoaded();
|
|
51
53
|
this.onUpdate.emit(this._state);
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
@@ -79,6 +81,7 @@ class OTDoc extends BaseDoc {
|
|
|
79
81
|
this._state = applyChangesToState(this._state, changes);
|
|
80
82
|
this._pendingChanges.push(...changes);
|
|
81
83
|
}
|
|
84
|
+
this._checkLoaded();
|
|
82
85
|
this.onUpdate.emit(this._state);
|
|
83
86
|
}
|
|
84
87
|
/**
|
package/dist/client/Patches.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Unsubscriber, Signal } from '../event-signal.js';
|
|
|
2
2
|
import { JSONPatchOp } from '../json-patch/types.js';
|
|
3
3
|
import { Change } from '../types.js';
|
|
4
4
|
import { ClientAlgorithm } from './ClientAlgorithm.js';
|
|
5
|
-
import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-
|
|
5
|
+
import { P as PatchesDocOptions, a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
6
6
|
import { AlgorithmName } from './PatchesStore.js';
|
|
7
7
|
import '../json-patch/JSONPatch.js';
|
|
8
8
|
import '@dabble/delta';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../event-signal.js';
|
|
2
2
|
import '../json-patch/types.js';
|
|
3
3
|
import '../types.js';
|
|
4
|
-
export { O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-
|
|
4
|
+
export { O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-BfVJNeCi.js';
|
|
5
5
|
import '../json-patch/JSONPatch.js';
|
|
6
6
|
import '@dabble/delta';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AlgorithmName } from './PatchesStore.js';
|
|
2
2
|
import { Patches } from './Patches.js';
|
|
3
|
-
import { P as PatchesDocOptions } from '../BaseDoc-
|
|
3
|
+
import { P as PatchesDocOptions } from '../BaseDoc-BfVJNeCi.js';
|
|
4
4
|
import '../types.js';
|
|
5
5
|
import '../json-patch/JSONPatch.js';
|
|
6
6
|
import '@dabble/delta';
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-
|
|
1
|
+
export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from '../BaseDoc-BfVJNeCi.js';
|
|
2
2
|
export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches } from './factories.js';
|
|
3
3
|
export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore } from './IndexedDBStore.js';
|
|
4
4
|
export { OTIndexedDBStore } from './OTIndexedDBStore.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Delta } from '@dabble/delta';
|
|
2
|
-
export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from './BaseDoc-
|
|
2
|
+
export { B as BaseDoc, O as OTDoc, a as PatchesDoc, O as PatchesDocClass, P as PatchesDocOptions } from './BaseDoc-BfVJNeCi.js';
|
|
3
3
|
export { IndexedDBFactoryOptions, MultiAlgorithmFactoryOptions, MultiAlgorithmIndexedDBFactoryOptions, PatchesFactoryOptions, createLWWIndexedDBPatches, createLWWPatches, createMultiAlgorithmIndexedDBPatches, createMultiAlgorithmPatches, createOTIndexedDBPatches, createOTPatches } from './client/factories.js';
|
|
4
4
|
export { IDBStoreWrapper, IDBTransactionWrapper, IndexedDBStore } from './client/IndexedDBStore.js';
|
|
5
5
|
export { OTIndexedDBStore } from './client/OTIndexedDBStore.js';
|
|
@@ -29,7 +29,7 @@ export { createPathProxy, pathProxy } from './json-patch/pathProxy.js';
|
|
|
29
29
|
export { transformPatch } from './json-patch/transformPatch.js';
|
|
30
30
|
export { JSONPatch, PathLike, WriteOptions } from './json-patch/JSONPatch.js';
|
|
31
31
|
export { ApplyJSONPatchOptions, JSONPatchOpHandlerMap as JSONPatchCustomTypes, JSONPatchOp } from './json-patch/types.js';
|
|
32
|
-
export { Branch, BranchStatus, Change, ChangeInput, ChangeMutator, CommitChangesOptions, DeleteDocOptions, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy,
|
|
32
|
+
export { Branch, BranchStatus, Change, ChangeInput, ChangeMutator, CommitChangesOptions, DeleteDocOptions, DocSyncStatus, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy, SyncedDoc, VersionMetadata } from './types.js';
|
|
33
33
|
export { add } from './json-patch/ops/add.js';
|
|
34
34
|
export { copy } from './json-patch/ops/copy.js';
|
|
35
35
|
export { increment } from './json-patch/ops/increment.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Signal } from '../event-signal.js';
|
|
2
2
|
import { ConnectionState } from './protocol/types.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DocSyncStatus, SyncedDoc, Change } from '../types.js';
|
|
4
4
|
import { JSONRPCClient } from './protocol/JSONRPCClient.js';
|
|
5
5
|
import { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
|
|
6
6
|
import { WebSocketOptions } from './websocket/WebSocketTransport.js';
|
|
@@ -13,12 +13,13 @@ import '@dabble/delta';
|
|
|
13
13
|
import '../json-patch/types.js';
|
|
14
14
|
import './PatchesClient.js';
|
|
15
15
|
import '../utils/deferred.js';
|
|
16
|
-
import '../BaseDoc-
|
|
16
|
+
import '../BaseDoc-BfVJNeCi.js';
|
|
17
17
|
|
|
18
18
|
interface PatchesSyncState {
|
|
19
19
|
online: boolean;
|
|
20
20
|
connected: boolean;
|
|
21
|
-
|
|
21
|
+
syncStatus: DocSyncStatus;
|
|
22
|
+
syncError: Error | null;
|
|
22
23
|
}
|
|
23
24
|
interface PatchesSyncOptions {
|
|
24
25
|
subscribeFilter?: (docIds: string[]) => string[];
|
|
@@ -50,6 +51,7 @@ declare class PatchesSync {
|
|
|
50
51
|
/** Maps docId to the algorithm name used for that doc */
|
|
51
52
|
protected docAlgorithms: Map<string, AlgorithmName>;
|
|
52
53
|
protected _state: PatchesSyncState;
|
|
54
|
+
protected _syncedDocs: Record<string, SyncedDoc>;
|
|
53
55
|
/**
|
|
54
56
|
* Signal emitted when the sync state changes.
|
|
55
57
|
*/
|
|
@@ -65,6 +67,10 @@ declare class PatchesSync {
|
|
|
65
67
|
* Provides the pending changes that were discarded so the application can handle them.
|
|
66
68
|
*/
|
|
67
69
|
readonly onRemoteDocDeleted: Signal<(docId: string, pendingChanges: Change[]) => void>;
|
|
70
|
+
/**
|
|
71
|
+
* Signal emitted when the synced doc map changes.
|
|
72
|
+
*/
|
|
73
|
+
readonly onSyncedDocsChange: Signal<(synced: Record<string, SyncedDoc>) => void>;
|
|
68
74
|
constructor(patches: Patches, url: string, options?: PatchesSyncOptions | undefined);
|
|
69
75
|
/**
|
|
70
76
|
* Gets the algorithm for a document. Uses the open doc's algorithm if available,
|
|
@@ -83,6 +89,11 @@ declare class PatchesSync {
|
|
|
83
89
|
* Gets the current sync state.
|
|
84
90
|
*/
|
|
85
91
|
get state(): PatchesSyncState;
|
|
92
|
+
/**
|
|
93
|
+
* Map of all tracked documents and their current sync status.
|
|
94
|
+
* Updated immutably — new object reference on every change.
|
|
95
|
+
*/
|
|
96
|
+
get syncedDocs(): Record<string, SyncedDoc>;
|
|
86
97
|
/**
|
|
87
98
|
* Gets the JSON-RPC client for making custom RPC calls.
|
|
88
99
|
* Useful for application-specific methods not part of the Patches protocol.
|
|
@@ -142,6 +153,21 @@ declare class PatchesSync {
|
|
|
142
153
|
* Cleans up local state and notifies the application with any pending changes that were lost.
|
|
143
154
|
*/
|
|
144
155
|
protected _handleRemoteDocDeleted(docId: string): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Adds, updates, or removes a synced doc entry immutably and emits onSyncedChange.
|
|
158
|
+
* - Pass a full SyncedDoc to add a new entry or overwrite an existing one.
|
|
159
|
+
* - Pass a Partial<SyncedDoc> to merge into an existing entry (no-ops if doc not in map).
|
|
160
|
+
* - Pass undefined to remove the entry.
|
|
161
|
+
* No-ops if nothing actually changed.
|
|
162
|
+
*/
|
|
163
|
+
protected _updateSyncedDoc(docId: string, updates: Partial<SyncedDoc> | undefined, emit?: boolean): void;
|
|
164
|
+
protected _emitSyncedChange(): void;
|
|
165
|
+
/**
|
|
166
|
+
* Resets any docs with status 'syncing' back to a stable state on disconnect.
|
|
167
|
+
* Uses hasPending to decide: pending docs become 'synced' (they have local data),
|
|
168
|
+
* docs with no pending and no committed rev become 'unsynced'.
|
|
169
|
+
*/
|
|
170
|
+
protected _resetSyncingStatuses(): void;
|
|
145
171
|
/**
|
|
146
172
|
* Applies the subscribeFilter option to a list of doc IDs, returning the subset
|
|
147
173
|
* that should be sent to ws.subscribe/unsubscribe. Returns the full list if no filter is set.
|
package/dist/net/PatchesSync.js
CHANGED
|
@@ -11,9 +11,17 @@ import { breakChangesIntoBatches } from "../algorithms/ot/shared/changeBatching.
|
|
|
11
11
|
import { BaseDoc } from "../client/BaseDoc.js";
|
|
12
12
|
import { Patches } from "../client/Patches.js";
|
|
13
13
|
import { signal } from "../event-signal.js";
|
|
14
|
+
import { isDocLoaded } from "../shared/utils.js";
|
|
14
15
|
import { blockable } from "../utils/concurrency.js";
|
|
15
16
|
import { PatchesWebSocket } from "./websocket/PatchesWebSocket.js";
|
|
16
17
|
import { onlineState } from "./websocket/onlineState.js";
|
|
18
|
+
const EMPTY_SYNCED_DOC = {
|
|
19
|
+
committedRev: 0,
|
|
20
|
+
hasPending: false,
|
|
21
|
+
syncStatus: "unsynced",
|
|
22
|
+
syncError: null,
|
|
23
|
+
isLoaded: false
|
|
24
|
+
};
|
|
17
25
|
_syncDoc_dec = [blockable], __receiveCommittedChanges_dec = [blockable];
|
|
18
26
|
class PatchesSync {
|
|
19
27
|
constructor(patches, url, options) {
|
|
@@ -27,7 +35,8 @@ class PatchesSync {
|
|
|
27
35
|
__publicField(this, "trackedDocs");
|
|
28
36
|
/** Maps docId to the algorithm name used for that doc */
|
|
29
37
|
__publicField(this, "docAlgorithms", /* @__PURE__ */ new Map());
|
|
30
|
-
__publicField(this, "_state", { online: false, connected: false,
|
|
38
|
+
__publicField(this, "_state", { online: false, connected: false, syncStatus: "unsynced", syncError: null });
|
|
39
|
+
__publicField(this, "_syncedDocs", {});
|
|
31
40
|
/**
|
|
32
41
|
* Signal emitted when the sync state changes.
|
|
33
42
|
*/
|
|
@@ -41,6 +50,10 @@ class PatchesSync {
|
|
|
41
50
|
* Provides the pending changes that were discarded so the application can handle them.
|
|
42
51
|
*/
|
|
43
52
|
__publicField(this, "onRemoteDocDeleted", signal());
|
|
53
|
+
/**
|
|
54
|
+
* Signal emitted when the synced doc map changes.
|
|
55
|
+
*/
|
|
56
|
+
__publicField(this, "onSyncedDocsChange", signal());
|
|
44
57
|
this.patches = patches;
|
|
45
58
|
this.maxPayloadBytes = options?.maxPayloadBytes;
|
|
46
59
|
this.maxStorageBytes = options?.maxStorageBytes ?? patches.docOptions?.maxStorageBytes;
|
|
@@ -93,6 +106,13 @@ class PatchesSync {
|
|
|
93
106
|
get state() {
|
|
94
107
|
return this._state;
|
|
95
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Map of all tracked documents and their current sync status.
|
|
111
|
+
* Updated immutably — new object reference on every change.
|
|
112
|
+
*/
|
|
113
|
+
get syncedDocs() {
|
|
114
|
+
return this._syncedDocs;
|
|
115
|
+
}
|
|
96
116
|
/**
|
|
97
117
|
* Gets the JSON-RPC client for making custom RPC calls.
|
|
98
118
|
* Useful for application-specific methods not part of the Patches protocol.
|
|
@@ -106,6 +126,9 @@ class PatchesSync {
|
|
|
106
126
|
*/
|
|
107
127
|
updateState(update) {
|
|
108
128
|
const newState = { ...this._state, ...update };
|
|
129
|
+
if (newState.syncStatus !== "error" && newState.syncError) {
|
|
130
|
+
newState.syncError = null;
|
|
131
|
+
}
|
|
109
132
|
if (!isEqual(this._state, newState)) {
|
|
110
133
|
this._state = newState;
|
|
111
134
|
this.onStateChange.emit(this._state);
|
|
@@ -119,8 +142,9 @@ class PatchesSync {
|
|
|
119
142
|
await this.ws.connect();
|
|
120
143
|
} catch (err) {
|
|
121
144
|
console.error("PatchesSync connection failed:", err);
|
|
122
|
-
|
|
123
|
-
this.
|
|
145
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
146
|
+
this.updateState({ connected: false, syncStatus: "error", syncError: error });
|
|
147
|
+
this.onError.emit(error);
|
|
124
148
|
throw err;
|
|
125
149
|
}
|
|
126
150
|
}
|
|
@@ -129,14 +153,15 @@ class PatchesSync {
|
|
|
129
153
|
*/
|
|
130
154
|
disconnect() {
|
|
131
155
|
this.ws.disconnect();
|
|
132
|
-
this.updateState({ connected: false,
|
|
156
|
+
this.updateState({ connected: false, syncStatus: "unsynced" });
|
|
157
|
+
this._resetSyncingStatuses();
|
|
133
158
|
}
|
|
134
159
|
/**
|
|
135
160
|
* Syncs all known docs when initially connected.
|
|
136
161
|
*/
|
|
137
162
|
async syncAllKnownDocs() {
|
|
138
163
|
if (!this.state.connected) return;
|
|
139
|
-
this.updateState({
|
|
164
|
+
this.updateState({ syncStatus: "syncing" });
|
|
140
165
|
try {
|
|
141
166
|
const allTracked = [];
|
|
142
167
|
for (const algorithm of Object.values(this.patches.algorithms)) {
|
|
@@ -153,6 +178,23 @@ class PatchesSync {
|
|
|
153
178
|
const deletedDocs = allTracked.filter((t) => t.deleted);
|
|
154
179
|
const activeDocIds = activeDocs.map((t) => t.docId);
|
|
155
180
|
this.trackedDocs = new Set(activeDocIds);
|
|
181
|
+
const syncedEntries = {};
|
|
182
|
+
for (const doc of activeDocs) {
|
|
183
|
+
const algorithm = this._getAlgorithm(doc.docId);
|
|
184
|
+
const pending = await algorithm.getPendingToSend(doc.docId);
|
|
185
|
+
const entry = {
|
|
186
|
+
committedRev: doc.committedRev,
|
|
187
|
+
hasPending: pending != null && pending.length > 0,
|
|
188
|
+
syncStatus: doc.committedRev === 0 ? "unsynced" : "synced",
|
|
189
|
+
syncError: null,
|
|
190
|
+
isLoaded: false
|
|
191
|
+
};
|
|
192
|
+
const existing = this._syncedDocs[doc.docId];
|
|
193
|
+
entry.isLoaded = existing?.isLoaded || isDocLoaded(entry);
|
|
194
|
+
syncedEntries[doc.docId] = entry;
|
|
195
|
+
}
|
|
196
|
+
this._syncedDocs = syncedEntries;
|
|
197
|
+
this.onSyncedDocsChange.emit(this._syncedDocs);
|
|
156
198
|
if (activeDocIds.length > 0) {
|
|
157
199
|
try {
|
|
158
200
|
const subscribeIds = this._filterSubscribeIds(activeDocIds);
|
|
@@ -178,20 +220,22 @@ class PatchesSync {
|
|
|
178
220
|
}
|
|
179
221
|
});
|
|
180
222
|
await Promise.all([...activeSyncPromises, ...deletePromises]);
|
|
181
|
-
this.updateState({
|
|
223
|
+
this.updateState({ syncStatus: "synced" });
|
|
182
224
|
} catch (error) {
|
|
183
225
|
console.error("Error during global sync:", error);
|
|
184
|
-
|
|
185
|
-
this.
|
|
226
|
+
const syncError = error instanceof Error ? error : new Error(String(error));
|
|
227
|
+
this.updateState({ syncStatus: "error", syncError });
|
|
228
|
+
this.onError.emit(syncError);
|
|
186
229
|
}
|
|
187
230
|
}
|
|
188
231
|
async syncDoc(docId) {
|
|
189
232
|
if (!this.state.connected) return;
|
|
233
|
+
this._updateSyncedDoc(docId, { syncStatus: "syncing" });
|
|
190
234
|
const doc = this.patches.getOpenDoc(docId);
|
|
191
235
|
const algorithm = this._getAlgorithm(docId);
|
|
192
236
|
const baseDoc = doc;
|
|
193
237
|
if (baseDoc) {
|
|
194
|
-
baseDoc.
|
|
238
|
+
baseDoc.updateSyncStatus("syncing");
|
|
195
239
|
}
|
|
196
240
|
try {
|
|
197
241
|
const pending = await algorithm.getPendingToSend(docId);
|
|
@@ -207,23 +251,27 @@ class PatchesSync {
|
|
|
207
251
|
} else {
|
|
208
252
|
const snapshot = await this.ws.getDoc(docId);
|
|
209
253
|
await algorithm.store.saveDoc(docId, snapshot);
|
|
254
|
+
this._updateSyncedDoc(docId, { committedRev: snapshot.rev });
|
|
210
255
|
if (baseDoc) {
|
|
211
256
|
baseDoc.import({ ...snapshot, changes: [] });
|
|
212
257
|
}
|
|
213
258
|
}
|
|
214
259
|
}
|
|
260
|
+
this._updateSyncedDoc(docId, { syncStatus: "synced" });
|
|
215
261
|
if (baseDoc) {
|
|
216
|
-
baseDoc.
|
|
262
|
+
baseDoc.updateSyncStatus("synced");
|
|
217
263
|
}
|
|
218
264
|
} catch (err) {
|
|
219
265
|
if (this._isDocDeletedError(err)) {
|
|
220
266
|
await this._handleRemoteDocDeleted(docId);
|
|
221
267
|
return;
|
|
222
268
|
}
|
|
269
|
+
const syncError = err instanceof Error ? err : new Error(String(err));
|
|
270
|
+
this._updateSyncedDoc(docId, { syncStatus: "error", syncError });
|
|
223
271
|
console.error(`Error syncing doc ${docId}:`, err);
|
|
224
|
-
this.onError.emit(
|
|
272
|
+
this.onError.emit(syncError, { docId });
|
|
225
273
|
if (baseDoc) {
|
|
226
|
-
baseDoc.
|
|
274
|
+
baseDoc.updateSyncStatus("error", syncError);
|
|
227
275
|
}
|
|
228
276
|
}
|
|
229
277
|
}
|
|
@@ -261,13 +309,17 @@ class PatchesSync {
|
|
|
261
309
|
await algorithm.confirmSent(docId, batch);
|
|
262
310
|
pending = await algorithm.getPendingToSend(docId) ?? [];
|
|
263
311
|
}
|
|
312
|
+
const stillHasPending = pending != null && pending.length > 0;
|
|
313
|
+
this._updateSyncedDoc(docId, { hasPending: stillHasPending, syncStatus: "synced" });
|
|
264
314
|
} catch (err) {
|
|
265
315
|
if (this._isDocDeletedError(err)) {
|
|
266
316
|
await this._handleRemoteDocDeleted(docId);
|
|
267
317
|
return;
|
|
268
318
|
}
|
|
319
|
+
const flushError = err instanceof Error ? err : new Error(String(err));
|
|
320
|
+
this._updateSyncedDoc(docId, { syncStatus: "error", syncError: flushError });
|
|
269
321
|
console.error(`Flush failed for doc ${docId}:`, err);
|
|
270
|
-
this.onError.emit(
|
|
322
|
+
this.onError.emit(flushError, { docId });
|
|
271
323
|
throw err;
|
|
272
324
|
}
|
|
273
325
|
}
|
|
@@ -286,6 +338,10 @@ class PatchesSync {
|
|
|
286
338
|
const doc = this.patches.getOpenDoc(docId);
|
|
287
339
|
const algorithm = this._getAlgorithm(docId);
|
|
288
340
|
await algorithm.applyServerChanges(docId, serverChanges, doc);
|
|
341
|
+
if (serverChanges.length > 0) {
|
|
342
|
+
const lastRev = serverChanges[serverChanges.length - 1].rev;
|
|
343
|
+
this._updateSyncedDoc(docId, { committedRev: lastRev });
|
|
344
|
+
}
|
|
289
345
|
}
|
|
290
346
|
/**
|
|
291
347
|
* Initiates the deletion process for a document both locally and on the server.
|
|
@@ -309,10 +365,12 @@ class PatchesSync {
|
|
|
309
365
|
_handleConnectionChange(connectionState) {
|
|
310
366
|
const isConnected = connectionState === "connected";
|
|
311
367
|
const isConnecting = connectionState === "connecting";
|
|
312
|
-
const
|
|
313
|
-
this.updateState({ connected: isConnected,
|
|
368
|
+
const newSyncStatus = isConnected ? this._state.syncStatus : isConnecting ? this._state.syncStatus : "unsynced";
|
|
369
|
+
this.updateState({ connected: isConnected, syncStatus: newSyncStatus });
|
|
314
370
|
if (isConnected) {
|
|
315
371
|
void this.syncAllKnownDocs();
|
|
372
|
+
} else if (!isConnecting) {
|
|
373
|
+
this._resetSyncingStatuses();
|
|
316
374
|
}
|
|
317
375
|
}
|
|
318
376
|
async _handleDocsTracked(docIds) {
|
|
@@ -331,6 +389,21 @@ class PatchesSync {
|
|
|
331
389
|
}
|
|
332
390
|
}
|
|
333
391
|
}
|
|
392
|
+
for (const docId of newIds) {
|
|
393
|
+
const algorithm = this._getAlgorithm(docId);
|
|
394
|
+
const committedRev = await algorithm.getCommittedRev(docId);
|
|
395
|
+
const pending = await algorithm.getPendingToSend(docId);
|
|
396
|
+
this._updateSyncedDoc(
|
|
397
|
+
docId,
|
|
398
|
+
{
|
|
399
|
+
committedRev,
|
|
400
|
+
hasPending: pending != null && pending.length > 0,
|
|
401
|
+
syncStatus: committedRev === 0 ? "unsynced" : "synced"
|
|
402
|
+
},
|
|
403
|
+
false
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
this._emitSyncedChange();
|
|
334
407
|
if (this.state.connected) {
|
|
335
408
|
try {
|
|
336
409
|
const subscribeIds = this._filterSubscribeIds(newIds).filter((id) => !alreadySubscribed.has(id));
|
|
@@ -349,6 +422,8 @@ class PatchesSync {
|
|
|
349
422
|
if (!existingIds.length) return;
|
|
350
423
|
const subscribedBefore = this._getActiveSubscriptions();
|
|
351
424
|
existingIds.forEach((id) => this.trackedDocs.delete(id));
|
|
425
|
+
existingIds.forEach((id) => this._updateSyncedDoc(id, void 0, false));
|
|
426
|
+
this._emitSyncedChange();
|
|
352
427
|
const subscribedAfter = this._getActiveSubscriptions();
|
|
353
428
|
const unsubscribeIds = [...subscribedBefore].filter((id) => !subscribedAfter.has(id));
|
|
354
429
|
if (this.state.connected && unsubscribeIds.length) {
|
|
@@ -360,8 +435,10 @@ class PatchesSync {
|
|
|
360
435
|
}
|
|
361
436
|
}
|
|
362
437
|
async _handleDocChange(docId) {
|
|
363
|
-
if (!this.state.connected) return;
|
|
364
438
|
if (!this.trackedDocs.has(docId)) return;
|
|
439
|
+
this._updateSyncedDoc(docId, { hasPending: true }, !this.state.connected);
|
|
440
|
+
if (!this.state.connected) return;
|
|
441
|
+
this._updateSyncedDoc(docId, { syncStatus: "syncing" });
|
|
365
442
|
await this.flushDoc(docId);
|
|
366
443
|
}
|
|
367
444
|
/**
|
|
@@ -376,9 +453,54 @@ class PatchesSync {
|
|
|
376
453
|
await this.patches.closeDoc(docId);
|
|
377
454
|
}
|
|
378
455
|
this.trackedDocs.delete(docId);
|
|
456
|
+
this._updateSyncedDoc(docId, void 0);
|
|
379
457
|
await algorithm.confirmDeleteDoc(docId);
|
|
380
458
|
await this.onRemoteDocDeleted.emit(docId, pendingChanges);
|
|
381
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Adds, updates, or removes a synced doc entry immutably and emits onSyncedChange.
|
|
462
|
+
* - Pass a full SyncedDoc to add a new entry or overwrite an existing one.
|
|
463
|
+
* - Pass a Partial<SyncedDoc> to merge into an existing entry (no-ops if doc not in map).
|
|
464
|
+
* - Pass undefined to remove the entry.
|
|
465
|
+
* No-ops if nothing actually changed.
|
|
466
|
+
*/
|
|
467
|
+
_updateSyncedDoc(docId, updates, emit = true) {
|
|
468
|
+
if (updates === void 0) {
|
|
469
|
+
if (!(docId in this._syncedDocs)) return;
|
|
470
|
+
this._syncedDocs = { ...this._syncedDocs };
|
|
471
|
+
delete this._syncedDocs[docId];
|
|
472
|
+
} else {
|
|
473
|
+
const updated = { ...EMPTY_SYNCED_DOC, ...this._syncedDocs[docId], ...updates };
|
|
474
|
+
if (updated.syncStatus !== "error" && updated.syncError) {
|
|
475
|
+
updated.syncError = null;
|
|
476
|
+
}
|
|
477
|
+
if (!updated.isLoaded) {
|
|
478
|
+
updated.isLoaded = isDocLoaded(updated);
|
|
479
|
+
}
|
|
480
|
+
if (isEqual(this._syncedDocs[docId], updated)) return;
|
|
481
|
+
this._syncedDocs = { ...this._syncedDocs, [docId]: updated };
|
|
482
|
+
}
|
|
483
|
+
if (emit) this._emitSyncedChange();
|
|
484
|
+
}
|
|
485
|
+
_emitSyncedChange() {
|
|
486
|
+
this.onSyncedDocsChange.emit(this._syncedDocs);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Resets any docs with status 'syncing' back to a stable state on disconnect.
|
|
490
|
+
* Uses hasPending to decide: pending docs become 'synced' (they have local data),
|
|
491
|
+
* docs with no pending and no committed rev become 'unsynced'.
|
|
492
|
+
*/
|
|
493
|
+
_resetSyncingStatuses() {
|
|
494
|
+
let changed = false;
|
|
495
|
+
for (const [docId, doc] of Object.entries(this._syncedDocs)) {
|
|
496
|
+
if (doc.syncStatus === "syncing") {
|
|
497
|
+
const newStatus = doc.committedRev === 0 && !doc.hasPending ? "unsynced" : "synced";
|
|
498
|
+
this._updateSyncedDoc(docId, { syncStatus: newStatus }, false);
|
|
499
|
+
changed = true;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (changed) this._emitSyncedChange();
|
|
503
|
+
}
|
|
382
504
|
/**
|
|
383
505
|
* Applies the subscribeFilter option to a list of doc IDs, returning the subset
|
|
384
506
|
* that should be sent to ws.subscribe/unsubscribe. Returns the full list if no filter is set.
|
package/dist/net/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import '../event-signal.js';
|
|
|
17
17
|
import '../algorithms/ot/shared/changeBatching.js';
|
|
18
18
|
import '../client/ClientAlgorithm.js';
|
|
19
19
|
import '../json-patch/types.js';
|
|
20
|
-
import '../BaseDoc-
|
|
20
|
+
import '../BaseDoc-BfVJNeCi.js';
|
|
21
21
|
import '../client/PatchesStore.js';
|
|
22
22
|
import '../client/Patches.js';
|
|
23
23
|
import '../server/types.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Patches, OpenDocOptions } from '../client/Patches.js';
|
|
2
|
-
import { a as PatchesDoc } from '../BaseDoc-
|
|
2
|
+
import { a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
3
3
|
import '../event-signal.js';
|
|
4
4
|
import '../json-patch/types.js';
|
|
5
5
|
import '../types.js';
|
package/dist/shared/utils.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { SyncedDoc } from '../types.js';
|
|
2
|
+
import '../json-patch/JSONPatch.js';
|
|
3
|
+
import '@dabble/delta';
|
|
4
|
+
import '../json-patch/types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns true if a document has completed its initial load — i.e., it has data
|
|
8
|
+
* to display (server data, cached data, or local changes) or sync has resolved.
|
|
9
|
+
*/
|
|
10
|
+
declare function isDocLoaded(doc: Pick<SyncedDoc, 'committedRev' | 'hasPending' | 'syncStatus'>): boolean;
|
|
1
11
|
/**
|
|
2
12
|
* Resolves a path template by replacing `:param` placeholders with values.
|
|
3
13
|
*
|
|
@@ -19,4 +29,4 @@ declare function fillPath(template: string, params: Record<string, string>): str
|
|
|
19
29
|
*/
|
|
20
30
|
declare function areSetsEqual(a: Set<string>, b: Set<string>): boolean;
|
|
21
31
|
|
|
22
|
-
export { areSetsEqual, fillPath };
|
|
32
|
+
export { areSetsEqual, fillPath, isDocLoaded };
|
package/dist/shared/utils.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import "../chunk-IZ2YBCUP.js";
|
|
2
|
+
function isDocLoaded(doc) {
|
|
3
|
+
return doc.committedRev > 0 || doc.hasPending || doc.syncStatus === "synced" || doc.syncStatus === "error";
|
|
4
|
+
}
|
|
2
5
|
function fillPath(template, params) {
|
|
3
6
|
return template.replace(/:(\w+)/g, (match, name) => {
|
|
4
7
|
const value = params[name];
|
|
@@ -18,5 +21,6 @@ function areSetsEqual(a, b) {
|
|
|
18
21
|
}
|
|
19
22
|
export {
|
|
20
23
|
areSetsEqual,
|
|
21
|
-
fillPath
|
|
24
|
+
fillPath,
|
|
25
|
+
isDocLoaded
|
|
22
26
|
};
|
package/dist/solid/context.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import '../types.js';
|
|
|
7
7
|
import '../json-patch/JSONPatch.js';
|
|
8
8
|
import '@dabble/delta';
|
|
9
9
|
import '../client/ClientAlgorithm.js';
|
|
10
|
-
import '../BaseDoc-
|
|
10
|
+
import '../BaseDoc-BfVJNeCi.js';
|
|
11
11
|
import '../client/PatchesStore.js';
|
|
12
12
|
import '../net/protocol/types.js';
|
|
13
13
|
import '../net/protocol/JSONRPCClient.js';
|
package/dist/solid/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import '../types.js';
|
|
|
11
11
|
import '../json-patch/JSONPatch.js';
|
|
12
12
|
import '@dabble/delta';
|
|
13
13
|
import '../client/ClientAlgorithm.js';
|
|
14
|
-
import '../BaseDoc-
|
|
14
|
+
import '../BaseDoc-BfVJNeCi.js';
|
|
15
15
|
import '../client/PatchesStore.js';
|
|
16
16
|
import '../net/PatchesSync.js';
|
|
17
17
|
import '../net/protocol/types.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Accessor } from 'solid-js';
|
|
2
2
|
import { OpenDocOptions } from '../client/Patches.js';
|
|
3
|
-
import { a as PatchesDoc } from '../BaseDoc-
|
|
3
|
+
import { a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
4
4
|
import { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
5
5
|
import { ChangeMutator } from '../types.js';
|
|
6
6
|
import '../event-signal.js';
|
package/dist/solid/primitives.js
CHANGED
|
@@ -11,7 +11,7 @@ import { JSONPatch } from "../json-patch/JSONPatch.js";
|
|
|
11
11
|
import { usePatchesContext } from "./context.js";
|
|
12
12
|
import { getDocManager } from "./doc-manager.js";
|
|
13
13
|
function createDocReactiveState(options) {
|
|
14
|
-
const { initialLoading = true, transformState, changeBehavior } = options;
|
|
14
|
+
const { initialLoading = true, hasSyncContext = false, transformState, changeBehavior } = options;
|
|
15
15
|
const [doc, setDoc] = createSignal(void 0);
|
|
16
16
|
const [data, setData] = createSignal(void 0);
|
|
17
17
|
const [loading, setLoading] = createSignal(initialLoading);
|
|
@@ -20,6 +20,19 @@ function createDocReactiveState(options) {
|
|
|
20
20
|
const [hasPending, setHasPending] = createSignal(false);
|
|
21
21
|
function setupDoc(patchesDoc) {
|
|
22
22
|
setDoc(patchesDoc);
|
|
23
|
+
let loaded = false;
|
|
24
|
+
function updateLoading() {
|
|
25
|
+
if (loaded) return;
|
|
26
|
+
if (patchesDoc.isLoaded) {
|
|
27
|
+
loaded = true;
|
|
28
|
+
setLoading(false);
|
|
29
|
+
} else if (patchesDoc.syncStatus === "syncing") {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
} else if (!hasSyncContext) {
|
|
32
|
+
loaded = true;
|
|
33
|
+
setLoading(false);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
23
36
|
const unsubState = patchesDoc.subscribe((state) => {
|
|
24
37
|
if (transformState && state) {
|
|
25
38
|
state = transformState(state, patchesDoc);
|
|
@@ -27,12 +40,12 @@ function createDocReactiveState(options) {
|
|
|
27
40
|
setData(() => state);
|
|
28
41
|
setRev(patchesDoc.committedRev);
|
|
29
42
|
setHasPending(patchesDoc.hasPending);
|
|
43
|
+
updateLoading();
|
|
30
44
|
});
|
|
31
|
-
const unsubSync = patchesDoc.
|
|
32
|
-
|
|
33
|
-
setError(
|
|
45
|
+
const unsubSync = patchesDoc.onSyncStatus((status) => {
|
|
46
|
+
updateLoading();
|
|
47
|
+
setError(status === "error" ? patchesDoc.syncError : null);
|
|
34
48
|
});
|
|
35
|
-
setLoading(patchesDoc.syncing !== null);
|
|
36
49
|
return () => {
|
|
37
50
|
unsubState();
|
|
38
51
|
unsubSync();
|
|
@@ -92,12 +105,16 @@ function usePatchesDoc(docIdOrOptions, options) {
|
|
|
92
105
|
return _usePatchesDocLazy(docIdOrOptions ?? {});
|
|
93
106
|
}
|
|
94
107
|
function _usePatchesDocEager(docId, options) {
|
|
95
|
-
const { patches } = usePatchesContext();
|
|
108
|
+
const { patches, sync } = usePatchesContext();
|
|
96
109
|
const { autoClose = false, algorithm, metadata } = options;
|
|
97
110
|
const shouldUntrack = autoClose === "untrack";
|
|
98
111
|
const openDocOpts = { algorithm, metadata };
|
|
99
112
|
const manager = getDocManager(patches);
|
|
100
|
-
const { setupDoc, setError, setLoading, baseReturn } = createDocReactiveState({
|
|
113
|
+
const { setupDoc, setError, setLoading, baseReturn } = createDocReactiveState({
|
|
114
|
+
initialLoading: !!autoClose,
|
|
115
|
+
hasSyncContext: !!sync,
|
|
116
|
+
changeBehavior: "throw"
|
|
117
|
+
});
|
|
101
118
|
const docIdAccessor = toAccessor(docId);
|
|
102
119
|
if (autoClose) {
|
|
103
120
|
const [docResource] = createResource(docIdAccessor, async (id) => {
|
|
@@ -139,10 +156,11 @@ function _usePatchesDocEager(docId, options) {
|
|
|
139
156
|
return baseReturn;
|
|
140
157
|
}
|
|
141
158
|
function _usePatchesDocLazy(options) {
|
|
142
|
-
const { patches } = usePatchesContext();
|
|
159
|
+
const { patches, sync } = usePatchesContext();
|
|
143
160
|
const { idProp } = options;
|
|
144
161
|
const { setupDoc, resetSignals, setError, setLoading, baseReturn } = createDocReactiveState({
|
|
145
162
|
initialLoading: false,
|
|
163
|
+
hasSyncContext: !!sync,
|
|
146
164
|
changeBehavior: "noop",
|
|
147
165
|
transformState: idProp ? (state, patchesDoc) => ({ ...state, [idProp]: patchesDoc.id }) : void 0
|
|
148
166
|
});
|
|
@@ -160,6 +178,7 @@ function _usePatchesDocLazy(options) {
|
|
|
160
178
|
await patches.closeDoc(prevPath);
|
|
161
179
|
}
|
|
162
180
|
setPath(docPath);
|
|
181
|
+
setLoading(true);
|
|
163
182
|
try {
|
|
164
183
|
const patchesDoc = await patches.openDoc(docPath, options2);
|
|
165
184
|
unsubscribe = setupDoc(patchesDoc);
|
|
@@ -197,11 +216,11 @@ function usePatchesSync() {
|
|
|
197
216
|
throw new Error("PatchesSync not found in context. Did you forget to pass sync to PatchesProvider?");
|
|
198
217
|
}
|
|
199
218
|
const [connected, setConnected] = createSignal(sync.state.connected);
|
|
200
|
-
const [syncing, setSyncing] = createSignal(sync.state.
|
|
219
|
+
const [syncing, setSyncing] = createSignal(sync.state.syncStatus === "syncing");
|
|
201
220
|
const [online, setOnline] = createSignal(sync.state.online);
|
|
202
221
|
const unsubscribe = sync.onStateChange((state) => {
|
|
203
222
|
setConnected(state.connected);
|
|
204
|
-
setSyncing(state.
|
|
223
|
+
setSyncing(state.syncStatus === "syncing");
|
|
205
224
|
setOnline(state.online);
|
|
206
225
|
});
|
|
207
226
|
onCleanup(() => {
|
|
@@ -219,12 +238,16 @@ function toAccessor(value) {
|
|
|
219
238
|
function createPatchesDoc(name) {
|
|
220
239
|
const Context = createContext();
|
|
221
240
|
function Provider(props) {
|
|
222
|
-
const { patches } = usePatchesContext();
|
|
241
|
+
const { patches, sync } = usePatchesContext();
|
|
223
242
|
const manager = getDocManager(patches);
|
|
224
243
|
const autoClose = props.autoClose ?? false;
|
|
225
244
|
const shouldUntrack = autoClose === "untrack";
|
|
226
245
|
const openDocOpts = { algorithm: props.algorithm, metadata: props.metadata };
|
|
227
|
-
const { setupDoc, setError, setLoading, baseReturn } = createDocReactiveState({
|
|
246
|
+
const { setupDoc, setError, setLoading, baseReturn } = createDocReactiveState({
|
|
247
|
+
initialLoading: !!autoClose,
|
|
248
|
+
hasSyncContext: !!sync,
|
|
249
|
+
changeBehavior: "throw"
|
|
250
|
+
});
|
|
228
251
|
const docIdAccessor = toAccessor(props.docId);
|
|
229
252
|
if (autoClose) {
|
|
230
253
|
const [docResource] = createResource(docIdAccessor, async (id) => {
|
package/dist/solid/utils.d.ts
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -57,13 +57,30 @@ interface PatchesSnapshot<T = any> extends PatchesState<T> {
|
|
|
57
57
|
changes: Change[];
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
60
|
+
* Sync status for a document, used by both PatchesDoc and PatchesSync's SyncedDoc.
|
|
61
|
+
* - `'unsynced'` — not yet synced (initial state, or disconnected)
|
|
62
|
+
* - `'syncing'` — actively syncing with the server
|
|
63
|
+
* - `'synced'` — up to date with the server
|
|
64
|
+
* - `'error'` — sync failed (see syncError for details)
|
|
65
65
|
*/
|
|
66
|
-
type
|
|
66
|
+
type DocSyncStatus = 'unsynced' | 'syncing' | 'synced' | 'error';
|
|
67
|
+
/**
|
|
68
|
+
* Represents the synced state of a document.
|
|
69
|
+
* @property committedRev - The last committed revision number from the server.
|
|
70
|
+
* @property hasPending - Whether there are local changes that haven't been committed yet.
|
|
71
|
+
* @property syncStatus - The current sync status of the document.
|
|
72
|
+
* @property syncError - The error from the last failed sync attempt, if any.
|
|
73
|
+
* @property isLoaded - Whether the document has completed its initial load. Sticky: once true, never reverts to false
|
|
74
|
+
* within a sync lifecycle. A document is considered loaded when it has data to display (server data, cached data,
|
|
75
|
+
* or local changes) or sync has resolved (successfully or with error).
|
|
76
|
+
*/
|
|
77
|
+
interface SyncedDoc {
|
|
78
|
+
committedRev: number;
|
|
79
|
+
hasPending: boolean;
|
|
80
|
+
syncStatus: DocSyncStatus;
|
|
81
|
+
syncError: Error | null;
|
|
82
|
+
isLoaded: boolean;
|
|
83
|
+
}
|
|
67
84
|
/** Status options for a branch */
|
|
68
85
|
type BranchStatus = 'open' | 'closed' | 'merged' | 'archived' | 'abandoned';
|
|
69
86
|
interface Branch {
|
|
@@ -220,4 +237,4 @@ type PathProxy<T = any> = IsAny<T> extends true ? DeepPathProxy : {
|
|
|
220
237
|
*/
|
|
221
238
|
type ChangeMutator<T> = (patch: JSONPatch, root: PathProxy<T>) => void;
|
|
222
239
|
|
|
223
|
-
export type { Branch, BranchStatus, Change, ChangeInput, ChangeMutator, CommitChangesOptions, DeleteDocOptions, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy,
|
|
240
|
+
export type { Branch, BranchStatus, Change, ChangeInput, ChangeMutator, CommitChangesOptions, DeleteDocOptions, DocSyncStatus, DocumentTombstone, EditableBranchMetadata, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesSnapshot, PatchesState, PathProxy, SyncedDoc, VersionMetadata };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ShallowRef, Ref, MaybeRef } from 'vue';
|
|
2
2
|
import { OpenDocOptions } from '../client/Patches.js';
|
|
3
|
-
import { a as PatchesDoc } from '../BaseDoc-
|
|
3
|
+
import { a as PatchesDoc } from '../BaseDoc-BfVJNeCi.js';
|
|
4
4
|
import { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
5
5
|
import { ChangeMutator } from '../types.js';
|
|
6
6
|
import '../event-signal.js';
|
package/dist/vue/composables.js
CHANGED
|
@@ -12,7 +12,7 @@ import { JSONPatch } from "../json-patch/JSONPatch.js";
|
|
|
12
12
|
import { usePatchesContext } from "./provider.js";
|
|
13
13
|
import { getDocManager } from "./doc-manager.js";
|
|
14
14
|
function createDocReactiveState(options) {
|
|
15
|
-
const { initialLoading = true, transformState, changeBehavior } = options;
|
|
15
|
+
const { initialLoading = true, hasSyncContext = false, transformState, changeBehavior } = options;
|
|
16
16
|
const doc = ref(void 0);
|
|
17
17
|
const data = shallowRef(void 0);
|
|
18
18
|
const loading = ref(initialLoading);
|
|
@@ -21,6 +21,19 @@ function createDocReactiveState(options) {
|
|
|
21
21
|
const hasPending = ref(false);
|
|
22
22
|
function setupDoc(patchesDoc) {
|
|
23
23
|
doc.value = patchesDoc;
|
|
24
|
+
let loaded = false;
|
|
25
|
+
function updateLoading() {
|
|
26
|
+
if (loaded) return;
|
|
27
|
+
if (patchesDoc.isLoaded) {
|
|
28
|
+
loaded = true;
|
|
29
|
+
loading.value = false;
|
|
30
|
+
} else if (patchesDoc.syncStatus === "syncing") {
|
|
31
|
+
loading.value = true;
|
|
32
|
+
} else if (!hasSyncContext) {
|
|
33
|
+
loaded = true;
|
|
34
|
+
loading.value = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
24
37
|
const unsubState = patchesDoc.subscribe((state) => {
|
|
25
38
|
if (transformState && state) {
|
|
26
39
|
state = transformState(state, patchesDoc);
|
|
@@ -28,12 +41,12 @@ function createDocReactiveState(options) {
|
|
|
28
41
|
data.value = state;
|
|
29
42
|
rev.value = patchesDoc.committedRev;
|
|
30
43
|
hasPending.value = patchesDoc.hasPending;
|
|
44
|
+
updateLoading();
|
|
31
45
|
});
|
|
32
|
-
const unsubSync = patchesDoc.
|
|
33
|
-
|
|
34
|
-
error.value =
|
|
46
|
+
const unsubSync = patchesDoc.onSyncStatus((status) => {
|
|
47
|
+
updateLoading();
|
|
48
|
+
error.value = status === "error" ? patchesDoc.syncError : null;
|
|
35
49
|
});
|
|
36
|
-
loading.value = patchesDoc.syncing !== null;
|
|
37
50
|
return () => {
|
|
38
51
|
unsubState();
|
|
39
52
|
unsubSync();
|
|
@@ -75,12 +88,16 @@ function usePatchesDoc(docIdOrOptions, options) {
|
|
|
75
88
|
return _usePatchesDocLazy(docIdOrOptions ?? {});
|
|
76
89
|
}
|
|
77
90
|
function _usePatchesDocEager(docId, options) {
|
|
78
|
-
const { patches } = usePatchesContext();
|
|
91
|
+
const { patches, sync } = usePatchesContext();
|
|
79
92
|
const { autoClose = false, algorithm, metadata } = options;
|
|
80
93
|
const shouldUntrack = autoClose === "untrack";
|
|
81
94
|
const openDocOpts = { algorithm, metadata };
|
|
82
95
|
const manager = getDocManager(patches);
|
|
83
|
-
const { setupDoc, baseReturn } = createDocReactiveState({
|
|
96
|
+
const { setupDoc, baseReturn } = createDocReactiveState({
|
|
97
|
+
initialLoading: !!autoClose,
|
|
98
|
+
hasSyncContext: !!sync,
|
|
99
|
+
changeBehavior: "throw"
|
|
100
|
+
});
|
|
84
101
|
let unsubscribe = null;
|
|
85
102
|
if (autoClose) {
|
|
86
103
|
manager.openDoc(patches, docId, openDocOpts).then((patchesDoc) => {
|
|
@@ -110,10 +127,11 @@ function _usePatchesDocEager(docId, options) {
|
|
|
110
127
|
return baseReturn;
|
|
111
128
|
}
|
|
112
129
|
function _usePatchesDocLazy(options) {
|
|
113
|
-
const { patches } = usePatchesContext();
|
|
130
|
+
const { patches, sync } = usePatchesContext();
|
|
114
131
|
const { idProp } = options;
|
|
115
|
-
const { setupDoc, resetRefs, baseReturn } = createDocReactiveState({
|
|
132
|
+
const { setupDoc, resetRefs, loading, baseReturn } = createDocReactiveState({
|
|
116
133
|
initialLoading: false,
|
|
134
|
+
hasSyncContext: !!sync,
|
|
117
135
|
changeBehavior: "noop",
|
|
118
136
|
transformState: idProp ? (state, patchesDoc) => ({ ...state, [idProp]: patchesDoc.id }) : void 0
|
|
119
137
|
});
|
|
@@ -131,12 +149,13 @@ function _usePatchesDocLazy(options) {
|
|
|
131
149
|
await patches.closeDoc(prevPath);
|
|
132
150
|
}
|
|
133
151
|
path.value = docPath;
|
|
152
|
+
loading.value = true;
|
|
134
153
|
try {
|
|
135
154
|
const patchesDoc = await patches.openDoc(docPath, options2);
|
|
136
155
|
unsubscribe = setupDoc(patchesDoc);
|
|
137
156
|
} catch (err) {
|
|
138
157
|
baseReturn.error.value = err;
|
|
139
|
-
|
|
158
|
+
loading.value = false;
|
|
140
159
|
}
|
|
141
160
|
}
|
|
142
161
|
async function close() {
|
|
@@ -168,11 +187,11 @@ function usePatchesSync() {
|
|
|
168
187
|
throw new Error("PatchesSync not found in context. Did you forget to pass sync to providePatchesContext()?");
|
|
169
188
|
}
|
|
170
189
|
const connected = ref(sync.state.connected);
|
|
171
|
-
const syncing = ref(sync.state.
|
|
190
|
+
const syncing = ref(sync.state.syncStatus === "syncing");
|
|
172
191
|
const online = ref(sync.state.online);
|
|
173
192
|
const unsubscribe = sync.onStateChange((state) => {
|
|
174
193
|
connected.value = state.connected;
|
|
175
|
-
syncing.value = state.
|
|
194
|
+
syncing.value = state.syncStatus === "syncing";
|
|
176
195
|
online.value = state.online;
|
|
177
196
|
});
|
|
178
197
|
onBeforeUnmount(() => {
|
|
@@ -188,12 +207,16 @@ function createDocInjectionKey(name) {
|
|
|
188
207
|
return Symbol(`patches-doc-${name}`);
|
|
189
208
|
}
|
|
190
209
|
function providePatchesDoc(name, docId, options = {}) {
|
|
191
|
-
const { patches } = usePatchesContext();
|
|
210
|
+
const { patches, sync } = usePatchesContext();
|
|
192
211
|
const { autoClose = false, algorithm, metadata } = options;
|
|
193
212
|
const shouldUntrack = autoClose === "untrack";
|
|
194
213
|
const openDocOpts = { algorithm, metadata };
|
|
195
214
|
const manager = getDocManager(patches);
|
|
196
|
-
const { setupDoc, baseReturn } = createDocReactiveState({
|
|
215
|
+
const { setupDoc, baseReturn } = createDocReactiveState({
|
|
216
|
+
initialLoading: !!autoClose,
|
|
217
|
+
hasSyncContext: !!sync,
|
|
218
|
+
changeBehavior: "throw"
|
|
219
|
+
});
|
|
197
220
|
const currentDocId = ref(unref(docId));
|
|
198
221
|
let unsubscribe = null;
|
|
199
222
|
async function initDoc(id) {
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import '../types.js';
|
|
|
11
11
|
import '../json-patch/JSONPatch.js';
|
|
12
12
|
import '@dabble/delta';
|
|
13
13
|
import '../client/ClientAlgorithm.js';
|
|
14
|
-
import '../BaseDoc-
|
|
14
|
+
import '../BaseDoc-BfVJNeCi.js';
|
|
15
15
|
import '../client/PatchesStore.js';
|
|
16
16
|
import '../net/PatchesSync.js';
|
|
17
17
|
import '../net/protocol/types.js';
|
package/dist/vue/provider.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import '../types.js';
|
|
|
7
7
|
import '../json-patch/JSONPatch.js';
|
|
8
8
|
import '@dabble/delta';
|
|
9
9
|
import '../client/ClientAlgorithm.js';
|
|
10
|
-
import '../BaseDoc-
|
|
10
|
+
import '../BaseDoc-BfVJNeCi.js';
|
|
11
11
|
import '../client/PatchesStore.js';
|
|
12
12
|
import '../net/protocol/types.js';
|
|
13
13
|
import '../net/protocol/JSONRPCClient.js';
|
package/dist/vue/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dabble/patches",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
|
|
5
5
|
"author": "Jacob Wright <jacwright@gmail.com>",
|
|
6
6
|
"bugs": {
|